diff options
author | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-01-20 01:29:50 +0000 |
---|---|---|
committer | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-01-20 01:29:50 +0000 |
commit | 8362bf63dea22bbf6736609b0f49c152f975eb63 (patch) | |
tree | 0eea3928e39e50fae91d4e68b21b1e6cbae25604 /kexi/widget/tableview | |
download | koffice-8362bf63dea22bbf6736609b0f49c152f975eb63.tar.gz koffice-8362bf63dea22bbf6736609b0f49c152f975eb63.zip |
Added old abandoned KDE3 version of koffice
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/applications/koffice@1077364 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'kexi/widget/tableview')
42 files changed, 14247 insertions, 0 deletions
diff --git a/kexi/widget/tableview/Makefile.am b/kexi/widget/tableview/Makefile.am new file mode 100644 index 00000000..69bb400d --- /dev/null +++ b/kexi/widget/tableview/Makefile.am @@ -0,0 +1,49 @@ +include $(top_srcdir)/kexi/Makefile.global + +lib_LTLIBRARIES = libkexidatatable.la +libkexidatatable_la_SOURCES = kexidataawareobjectiface.cpp \ + kexitableview.cpp kexitableview_p.cpp kexidatatableview.cpp \ + kexicelleditorfactory.cpp kexitableedit.cpp \ + kexitableviewheader.cpp kexitableitem.cpp kexitableviewdata.cpp \ + kexidatetableedit.cpp kexitimetableedit.cpp kexidatetimetableedit.cpp \ + kexiinputtableedit.cpp kexiblobtableedit.cpp kexibooltableedit.cpp \ + kexicomboboxbase.cpp kexicomboboxtableedit.cpp kexicomboboxpopup.cpp \ + kexidataawarepropertyset.cpp kexitextformatter.cpp + +noinst_HEADERS = kexitableview_p.h + +libkexidatatable_la_LDFLAGS = $(all_libraries) $(VER_INFO) -Wnounresolved +libkexidatatable_la_LIBADD = $(top_builddir)/kexi/core/libkexicore.la $(top_builddir)/kexi/widget/utils/libkexiguiutils.la \ + $(top_builddir)/lib/koproperty/libkoproperty.la $(LIB_KDEUI) + +#TODO: remove libkexicore link when kexiutils arrive + +SUBDIRS = . + + +# kde_appsdir Where your application's menu entry (.desktop) should go to. +# kde_icondir Where your icon should go to - better use KDE_ICON. +# kde_sounddir Where your sounds should go to. +# kde_htmldir Where your docs should go to. (contains lang subdirs) +# kde_datadir Where you install application data. (Use a subdir) +# kde_locale Where translation files should go to. (contains lang subdirs) +# kde_cgidir Where cgi-bin executables should go to. +# kde_confdir Where config files should go to (system-wide ones with default values). +# kde_mimedir Where mimetypes .desktop files should go to. +# kde_servicesdir Where services .desktop files should go to. +# kde_servicetypesdir Where servicetypes .desktop files should go to. +# kde_toolbardir Where general toolbar icons should go to (deprecated, use KDE_ICON). +# kde_wallpaperdir Where general wallpapers should go to. +# kde_templatesdir Where templates for the "New" menu (Konqueror/KDesktop) should go to. +# kde_bindir Where executables should go to. Use bin_PROGRAMS or bin_SCRIPTS. +# kde_libdir Where shared libraries should go to. Use lib_LTLIBRARIES. +# kde_moduledir Where modules (e.g. parts) should go to. Use kde_module_LTLIBRARIES. +# kde_styledir Where Qt/KDE widget styles should go to (new in KDE 3). +# kde_designerdir Where Qt Designer plugins should go to (new in KDE 3). + +# set the include path for X, qt and KDE +INCLUDES= -I$(top_srcdir)/kexi -I$(top_srcdir)/kexi/core -I$(top_srcdir)/kexi/kexidb \ + -I$(top_srcdir)/lib -I$(top_srcdir)/lib/kofficecore -I$(top_srcdir)/lib/koproperty/ $(all_includes) + +METASOURCES = AUTO + diff --git a/kexi/widget/tableview/autonumber.png b/kexi/widget/tableview/autonumber.png Binary files differnew file mode 100644 index 00000000..23fff353 --- /dev/null +++ b/kexi/widget/tableview/autonumber.png diff --git a/kexi/widget/tableview/kexiblobtableedit.cpp b/kexi/widget/tableview/kexiblobtableedit.cpp new file mode 100644 index 00000000..db0799a4 --- /dev/null +++ b/kexi/widget/tableview/kexiblobtableedit.cpp @@ -0,0 +1,595 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Peter Simonsson <psn@linux.se> + Copyright (C) 2004, 2006 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 "kexiblobtableedit.h" + +#include <stdlib.h> + +#include <qdatastream.h> +#include <qfile.h> +#include <qpopupmenu.h> +#include <qtextedit.h> +#include <qlayout.h> +#include <qstatusbar.h> +#include <qlabel.h> +#include <qpixmap.h> +#include <qimage.h> +#include <qpainter.h> +#include <qtooltip.h> +#include <qapplication.h> +#include <qclipboard.h> +#include <qbuffer.h> + +#include <kdebug.h> +#include <ktempfile.h> +#include <kmimetype.h> +#include <kmimemagic.h> +#include <kuserprofile.h> +#include <kservice.h> +#include <kprocess.h> +#include <kopenwith.h> +#include <kurl.h> +#include <karrowbutton.h> +#include <klocale.h> +#include <kfiledialog.h> +#include <kio/job.h> +#include <kglobal.h> +#include <kiconloader.h> +#include <kpopupmenu.h> +#include <kstdaccel.h> + +#include <kexiutils/utils.h> +#include <widget/utils/kexidropdownbutton.h> +#include <widget/utils/kexicontextmenuutils.h> + +//! @internal +class KexiBlobTableEdit::Private +{ +public: + Private() + : popup(0) + , readOnly(false) + , setValueInternalEnabled(true) + { + } + + QByteArray value; + KexiDropDownButton *button; + QSize totalSize; + KexiImageContextMenu *popup; + bool readOnly : 1; //!< cached for slotUpdateActionsAvailabilityRequested() + bool setValueInternalEnabled : 1; //!< used to disable KexiBlobTableEdit::setValueInternal() +}; + +//====================================================== + +KexiBlobTableEdit::KexiBlobTableEdit(KexiTableViewColumn &column, QWidget *parent) + : KexiTableEdit(column, parent) + , d ( new Private() ) +{ + setName("KexiBlobTableEdit"); +// m_proc = 0; +// m_content = 0; + m_hasFocusableWidget = false; + d->button = new KexiDropDownButton( parentWidget() /*usually a viewport*/ ); + d->button->hide(); + QToolTip::add(d->button, i18n("Click to show available actions for this cell")); + + d->popup = new KexiImageContextMenu(this); + d->popup->installEventFilter(this); + if (column.columnInfo) + KexiImageContextMenu::updateTitle( d->popup, column.columnInfo->captionOrAliasOrName(), +//! @todo pixmaplabel icon is hardcoded... + "pixmaplabel" ); + d->button->setPopup( d->popup ); + + //force edit requested to start editing... (this will call slotUpdateActionsAvailabilityRequested()) + //connect(d->popup, SIGNAL(aboutToShow()), this, SIGNAL(editRequested())); + + connect(d->popup, SIGNAL(updateActionsAvailabilityRequested(bool&, bool&)), + this, SLOT(slotUpdateActionsAvailabilityRequested(bool&, bool&))); + + connect(d->popup, SIGNAL(insertFromFileRequested(const KURL&)), + this, SLOT(handleInsertFromFileAction(const KURL&))); + connect(d->popup, SIGNAL(saveAsRequested(const QString&)), + this, SLOT(handleSaveAsAction(const QString&))); + connect(d->popup, SIGNAL(cutRequested()), + this, SLOT(handleCutAction())); + connect(d->popup, SIGNAL(copyRequested()), + this, SLOT(handleCopyAction())); + connect(d->popup, SIGNAL(pasteRequested()), + this, SLOT(handlePasteAction())); + connect(d->popup, SIGNAL(clearRequested()), + this, SLOT(clear())); + connect(d->popup, SIGNAL(showPropertiesRequested()), + this, SLOT(handleShowPropertiesAction())); +} + +KexiBlobTableEdit::~KexiBlobTableEdit() +{ + delete d; +#if 0 + kdDebug() << "KexiBlobTableEdit: Cleaning up..." << endl; + if (m_tempFile) { + m_tempFile->unlink(); + //todo + } + delete m_proc; + m_proc = 0; + kdDebug() << "KexiBlobTableEdit: Ready." << endl; +#endif +} + +//! initializes this editor with \a add value +void KexiBlobTableEdit::setValueInternal(const QVariant& add, bool removeOld) +{ + if (!d->setValueInternalEnabled) + return; + if (removeOld) + d->value = add.toByteArray(); + else //do not add "m_origValue" to "add" as this is QByteArray + d->value = m_origValue.toByteArray(); + +#if 0 //todo? + QByteArray val = m_origValue.toByteArray(); + kdDebug() << "KexiBlobTableEdit: Size of BLOB: " << val.size() << endl; + m_tempFile = new KTempFile(); + m_tempFile->setAutoDelete(true); + kdDebug() << "KexiBlobTableEdit: Creating temporary file: " << m_tempFile->name() << endl; + m_tempFile->dataStream()->writeRawBytes(val.data(), val.size()); + m_tempFile->close(); + delete m_tempFile; + m_tempFile = 0; + + KMimeMagicResult* mmr = KMimeMagic::self()->findFileType(m_tempFile->name()); + kdDebug() << "KexiBlobTableEdit: Mimetype = " << mmr->mimeType() << endl; + + setViewWidget( new QWidget(this) ); +#endif +} + +bool KexiBlobTableEdit::valueIsNull() +{ +//TODO + d->value.size(); + return d->value.isEmpty(); +} + +bool KexiBlobTableEdit::valueIsEmpty() +{ +//TODO + return d->value.isEmpty(); +} + +QVariant +KexiBlobTableEdit::value() +{ + return d->value; +#if 0 + //todo +// ok = true; + + if(m_content && m_content->isModified()) + { + return QVariant(m_content->text()); + } + QByteArray value; + QFile f( m_tempFile->name() ); + f.open(IO_ReadOnly); + QDataStream stream(&f); + char* data = (char*) malloc(f.size()); + value.resize(f.size()); + stream.readRawBytes(data, f.size()); + value.duplicate(data, f.size()); + free(data); + kdDebug() << "KexiBlobTableEdit: Size of BLOB: " << value.size() << endl; + return QVariant(value); +#endif +} + +void KexiBlobTableEdit::paintFocusBorders( QPainter *p, QVariant &, int x, int y, int w, int h ) +{ +// d->currentEditorWidth = w; + if (!d->readOnly && w > d->button->width()) + w -= d->button->width(); + p->drawRect(x, y, w, h); +} + +void +KexiBlobTableEdit::setupContents( QPainter *p, bool focused, const QVariant& val, + QString &txt, int &align, int &x, int &y_offset, int &w, int &h ) +{ + Q_UNUSED(focused); + Q_UNUSED(txt); + Q_UNUSED(align); + +//! @todo optimize: load to m_pixmap, downsize + QPixmap pixmap; + x = 0; + w -= 1; //a place for border + h -= 1; //a place for border + if (p && val.canCast(QVariant::ByteArray) && pixmap.loadFromData(val.toByteArray())) { + KexiUtils::drawPixmap( *p, 0/*lineWidth*/, QRect(x, y_offset, w, h), + pixmap, Qt::AlignCenter, true/*scaledContents*/, true/*keepAspectRatio*/); + } +} + +bool KexiBlobTableEdit::cursorAtStart() +{ + return true; +} + +bool KexiBlobTableEdit::cursorAtEnd() +{ + return true; +} + +void KexiBlobTableEdit::handleInsertFromFileAction(const KURL& url) +{ + if (isReadOnly()) + return; + + QString fileName( url.isLocalFile() ? url.path() : url.prettyURL() ); + + //! @todo download the file if remote, then set fileName properly + QFile f(fileName); + if (!f.open(IO_ReadOnly)) { + //! @todo err msg + return; + } + QByteArray ba = f.readAll(); + if (f.status()!=IO_Ok) { + //! @todo err msg + f.close(); + return; + } + f.close(); +// m_valueMimeType = KImageIO::mimeType( fileName ); + setValueInternal( ba, true ); + signalEditRequested(); + //emit acceptRequested(); +} + +void KexiBlobTableEdit::handleAboutToSaveAsAction(QString& origFilename, QString& fileExtension, bool& dataIsEmpty) +{ + Q_UNUSED(origFilename); + Q_UNUSED(fileExtension); + dataIsEmpty = valueIsEmpty(); +//! @todo no fname stored for now +} + +void KexiBlobTableEdit::handleSaveAsAction(const QString& fileName) +{ + QFile f(fileName); + if (!f.open(IO_WriteOnly)) { + //! @todo err msg + return; + } + f.writeBlock( d->value ); + if (f.status()!=IO_Ok) { + //! @todo err msg + f.close(); + return; + } + f.close(); +} + +void KexiBlobTableEdit::handleCutAction() +{ + if (isReadOnly()) + return; + handleCopyAction(); + clear(); +} + +void KexiBlobTableEdit::handleCopyAction() +{ + executeCopyAction(d->value); +} + +void KexiBlobTableEdit::executeCopyAction(const QByteArray& data) +{ + QPixmap pixmap; + if (!pixmap.loadFromData(data)) + return; + qApp->clipboard()->setPixmap(pixmap, QClipboard::Clipboard); +} + +void KexiBlobTableEdit::handlePasteAction() +{ + if (isReadOnly()) + return; + QPixmap pm( qApp->clipboard()->pixmap(QClipboard::Clipboard) ); + QByteArray ba; + QBuffer buffer( ba ); + buffer.open( IO_WriteOnly ); + if (pm.save( &buffer, "PNG" )) {// write pixmap into ba in PNG format + setValueInternal( ba, true ); + } + else { + setValueInternal( QByteArray(), true ); + } + signalEditRequested(); + //emit acceptRequested(); + repaintRelatedCell(); +} + +void KexiBlobTableEdit::clear() +{ + setValueInternal( QByteArray(), true ); + signalEditRequested(); + //emit acceptRequested(); + repaintRelatedCell(); +} + +void KexiBlobTableEdit::handleShowPropertiesAction() +{ + //! @todo +} + +void KexiBlobTableEdit::showFocus( const QRect& r, bool readOnly ) +{ + d->readOnly = readOnly; //cache for slotUpdateActionsAvailabilityRequested() +// d->button->move( pos().x()+ width(), pos().y() ); + updateFocus( r ); +// d->button->setEnabled(!readOnly); + if (d->readOnly) + d->button->hide(); + else + d->button->show(); +} + +void KexiBlobTableEdit::resize(int w, int h) +{ + d->totalSize = QSize(w,h); + const int addWidth = d->readOnly ? 0 : d->button->width(); + QWidget::resize(w - addWidth, h); + if (!d->readOnly) + d->button->resize( h, h ); + m_rightMarginWhenFocused = m_rightMargin + addWidth; + QRect r( pos().x(), pos().y(), w+1, h+1 ); + r.moveBy(m_scrollView->contentsX(),m_scrollView->contentsY()); + updateFocus( r ); +//todo if (d->popup) { +//todo d->popup->updateSize(); +//todo } +} + +void KexiBlobTableEdit::updateFocus( const QRect& r ) +{ + if (!d->readOnly) { + if (d->button->width() > r.width()) + moveChild(d->button, r.right() + 1, r.top()); + else + moveChild(d->button, r.right() - d->button->width(), r.top() ); + } +} + +void KexiBlobTableEdit::hideFocus() +{ + d->button->hide(); +} + +QSize KexiBlobTableEdit::totalSize() const +{ + return d->totalSize; +} + +void KexiBlobTableEdit::slotUpdateActionsAvailabilityRequested(bool& valueIsNull, bool& valueIsReadOnly) +{ + emit editRequested(); + valueIsNull = this->valueIsNull(); + valueIsReadOnly = d->readOnly || isReadOnly(); +} + +void KexiBlobTableEdit::signalEditRequested() +{ + d->setValueInternalEnabled = false; + emit editRequested(); + d->setValueInternalEnabled = true; +} + +bool KexiBlobTableEdit::handleKeyPress( QKeyEvent* ke, bool editorActive ) +{ + Q_UNUSED(editorActive); + + const int k = ke->key(); + KKey kkey(ke); + if (!d->readOnly) { + if ((ke->state()==Qt::NoButton && k==Qt::Key_F4) + || (ke->state()==Qt::AltButton && k==Qt::Key_Down)) { + d->button->animateClick(); + QMouseEvent me( QEvent::MouseButtonPress, QPoint(2,2), Qt::LeftButton, Qt::NoButton ); + QApplication::sendEvent( d->button, &me ); + } + else if ((ke->state()==NoButton && (k==Qt::Key_F2 || k==Qt::Key_Space || k==Qt::Key_Enter || k==Qt::Key_Return))) { + d->popup->insertFromFile(); + } + else + return false; + } + else + return false; + return true; +} + +bool KexiBlobTableEdit::handleDoubleClick() +{ + d->popup->insertFromFile(); + return true; +} + +void KexiBlobTableEdit::handleCopyAction(const QVariant& value, const QVariant& visibleValue) +{ + Q_UNUSED(visibleValue); + executeCopyAction(value.toByteArray()); +} + +void KexiBlobTableEdit::handleAction(const QString& actionName) +{ + if (actionName=="edit_paste") { + d->popup->paste(); + } + else if (actionName=="edit_cut") { + emit editRequested(); + d->popup->cut(); + } +} + +bool KexiBlobTableEdit::eventFilter( QObject *o, QEvent *e ) +{ + if (o == d->popup && e->type()==QEvent::KeyPress) { + QKeyEvent* ke = static_cast<QKeyEvent*>(e); + const int state = ke->state(); + const int k = ke->key(); + if ( (state==Qt::NoButton && (k==Qt::Key_Tab || k==Qt::Key_Left || k==Qt::Key_Right)) + || (state==Qt::ShiftButton && k==Qt::Key_Backtab) + ) + { + d->popup->hide(); + QApplication::sendEvent( this, ke ); //re-send to move cursor + return true; + } + } + return false; +} + +KEXI_CELLEDITOR_FACTORY_ITEM_IMPL(KexiBlobEditorFactoryItem, KexiBlobTableEdit) + +//======================= +// KexiKIconTableEdit class is temporarily here: + +//! @internal +class KexiKIconTableEdit::Private +{ +public: + Private() + : pixmapCache(17, 17, false) + { + } + //! We've no editor widget that would store current value, so we do this here + QVariant currentValue; + + QCache<QPixmap> pixmapCache; +}; + +KexiKIconTableEdit::KexiKIconTableEdit(KexiTableViewColumn &column, QWidget *parent) + : KexiTableEdit(column, parent) + , d( new Private() ) +{ + setName("KexiKIconTableEdit"); + init(); +} + +KexiKIconTableEdit::~KexiKIconTableEdit() +{ + delete d; +} + +void KexiKIconTableEdit::init() +{ + m_hasFocusableWidget = false; + d->pixmapCache.setAutoDelete(true); +} + +void KexiKIconTableEdit::setValueInternal(const QVariant& /*add*/, bool /*removeOld*/) +{ + d->currentValue = m_origValue; +} + +bool KexiKIconTableEdit::valueIsNull() +{ + return d->currentValue.isNull(); +} + +bool KexiKIconTableEdit::valueIsEmpty() +{ + return d->currentValue.isNull(); +} + +QVariant KexiKIconTableEdit::value() +{ + return d->currentValue; +} + +void KexiKIconTableEdit::clear() +{ + d->currentValue = QVariant(); +} + +bool KexiKIconTableEdit::cursorAtStart() +{ + return true; +} + +bool KexiKIconTableEdit::cursorAtEnd() +{ + return true; +} + +void KexiKIconTableEdit::setupContents( QPainter *p, bool /*focused*/, const QVariant& val, + QString &/*txt*/, int &/*align*/, int &/*x*/, int &y_offset, int &w, int &h ) +{ + Q_UNUSED( y_offset ); + +#if 0 +#ifdef Q_WS_WIN + y_offset = -1; +#else + y_offset = 0; +#endif + int s = QMAX(h - 5, 12); + s = QMIN( h-3, s ); + s = QMIN( w-3, s );//avoid too large box + QRect r( QMAX( w/2 - s/2, 0 ) , h/2 - s/2 /*- 1*/, s, s); + p->setPen(QPen(colorGroup().text(), 1)); + p->drawRect(r); + if (val.asBool()) { + p->drawLine(r.x(), r.y(), r.right(), r.bottom()); + p->drawLine(r.x(), r.bottom(), r.right(), r.y()); + } +#endif + + QString key = val.toString(); + QPixmap *pix = 0; + if (!key.isEmpty() && !(pix = d->pixmapCache[ key ])) { + //cache pixmap + QPixmap pm = KGlobal::iconLoader()->loadIcon( key, KIcon::Small, + 0, KIcon::DefaultState, 0L, true/*canReturnNull*/ ); + if (!pm.isNull()) { + pix = new QPixmap(pm); + d->pixmapCache.insert(key, pix); + } + } + + if (p && pix) { + p->drawPixmap( (w-pix->width())/2, (h-pix->height())/2, *pix ); + } +} + +void KexiKIconTableEdit::handleCopyAction(const QVariant& value, const QVariant& visibleValue) +{ + Q_UNUSED(value); + Q_UNUSED(visibleValue); +} + +KEXI_CELLEDITOR_FACTORY_ITEM_IMPL(KexiKIconTableEditorFactoryItem, KexiKIconTableEdit) + +#include "kexiblobtableedit.moc" diff --git a/kexi/widget/tableview/kexiblobtableedit.h b/kexi/widget/tableview/kexiblobtableedit.h new file mode 100644 index 00000000..a44559be --- /dev/null +++ b/kexi/widget/tableview/kexiblobtableedit.h @@ -0,0 +1,170 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Peter Simonsson <psn@linux.se> + Copyright (C) 2004, 2006 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. + */ + +#ifndef _KEXIBLOBTABLEEDIT_H_ +#define _KEXIBLOBTABLEEDIT_H_ + +#include <qcstring.h> +#include <qcache.h> + +#include <kurl.h> + +#include "kexitableedit.h" +#include "kexicelleditorfactory.h" + +class KTempFile; +class KProcess; +class QTextEdit; + +class KexiBlobTableEdit : public KexiTableEdit +{ + Q_OBJECT + public: + KexiBlobTableEdit(KexiTableViewColumn &column, QWidget *parent=0); + virtual ~KexiBlobTableEdit(); + + bool valueIsNull(); + bool valueIsEmpty(); + + virtual QVariant value(); + + virtual bool cursorAtStart(); + virtual bool cursorAtEnd(); + + /*! Reimplemented: resizes a view(). */ + virtual void resize(int w, int h); + + virtual void showFocus( const QRect& r, bool readOnly ); + + virtual void hideFocus(); + + /*! \return total size of this editor, including popup button. */ + virtual QSize totalSize() const; + + virtual void paintFocusBorders( QPainter *p, QVariant &, int x, int y, int w, int h ); + + /*! Reimplemented to handle the key events. */ + virtual bool handleKeyPress( QKeyEvent* ke, bool editorActive ); + + /*! Handles double click request coming from the table view. + \return true if it has been consumed. + Reimplemented in KexiBlobTableEdit (to execute "insert file" action. */ + virtual bool handleDoubleClick(); + + /*! Handles action having standard name \a actionName. + Action could be: "edit_cut", "edit_paste", etc. */ + virtual void handleAction(const QString& actionName); + + /*! Handles copy action for value. The \a value is copied to clipboard in format appropriate + for the editor's impementation, e.g. for image cell it can be a pixmap. + \a visibleValue is unused here. Reimplemented after KexiTableEdit. */ + virtual void handleCopyAction(const QVariant& value, const QVariant& visibleValue); + + virtual void setupContents( QPainter *p, bool focused, const QVariant& val, + QString &txt, int &align, int &x, int &y_offset, int &w, int &h ); + + protected slots: + void slotUpdateActionsAvailabilityRequested(bool& valueIsNull, bool& valueIsReadOnly); + + void handleInsertFromFileAction(const KURL& url); + void handleAboutToSaveAsAction(QString& origFilename, QString& fileExtension, bool& dataIsEmpty); + void handleSaveAsAction(const QString& fileName); + void handleCutAction(); + void handleCopyAction(); + void handlePasteAction(); + virtual void clear(); + void handleShowPropertiesAction(); + + protected: + //! initializes this editor with \a add value + virtual void setValueInternal(const QVariant& add, bool removeOld); + + //todo QString openWithDlg(const QString& file); + //todo void execute(const QString& app, const QString& file); + + //! @internal + void updateFocus( const QRect& r ); + + void signalEditRequested(); + + //! @internal + void executeCopyAction(const QByteArray& data); + + virtual bool eventFilter( QObject *o, QEvent *e ); + + class Private; + Private *d; +//todo KTempFile* m_tempFile; +//todo KProcess* m_proc; +//todo QTextEdit *m_content; +}; + +KEXI_DECLARE_CELLEDITOR_FACTORY_ITEM(KexiBlobEditorFactoryItem) + + +//======================= +//This class is temporarily here: + +/*! @short Cell editor for displaying kde icon (using icon name provided as string). + Read only. +*/ +class KexiKIconTableEdit : public KexiTableEdit +{ + public: + KexiKIconTableEdit(KexiTableViewColumn &column, QWidget *parent=0); + + virtual ~KexiKIconTableEdit(); + + //! \return true if editor's value is null (not empty) + virtual bool valueIsNull(); + + //! \return true if editor's value is empty (not null). + //! Only few field types can accept "EMPTY" property + //! (check this with KexiDB::Field::hasEmptyProperty()), + virtual bool valueIsEmpty(); + + virtual QVariant value(); + + virtual bool cursorAtStart(); + virtual bool cursorAtEnd(); + + virtual void clear(); + + virtual void setupContents( QPainter *p, bool focused, const QVariant& val, + QString &txt, int &align, int &x, int &y_offset, int &w, int &h ); + + /*! Handles copy action for value. Does nothing. + \a visibleValue is unused here. Reimplemented after KexiTableEdit. */ + virtual void handleCopyAction(const QVariant& value, const QVariant& visibleValue); + + protected: + //! initializes this editor with \a add value + virtual void setValueInternal(const QVariant& add, bool removeOld); + + void showHintButton(); + void init(); + + class Private; + Private *d; +}; + +KEXI_DECLARE_CELLEDITOR_FACTORY_ITEM(KexiKIconTableEditorFactoryItem) + +#endif diff --git a/kexi/widget/tableview/kexibooltableedit.cpp b/kexi/widget/tableview/kexibooltableedit.cpp new file mode 100644 index 00000000..7b7bc0ac --- /dev/null +++ b/kexi/widget/tableview/kexibooltableedit.cpp @@ -0,0 +1,180 @@ +/* This file is part of the KDE project + Copyright (C) 2004 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 "kexibooltableedit.h" + +#include <kexidb/field.h> + +#include <qpainter.h> +#include <qapplication.h> +#include <qclipboard.h> + +#include <kglobal.h> +#include <klocale.h> +#include <kdebug.h> +#include <kglobalsettings.h> + + +KexiBoolTableEdit::KexiBoolTableEdit(KexiTableViewColumn &column, QWidget *parent) + : KexiTableEdit(column, parent) +{ + setName("KexiBoolTableEdit"); + kdDebug() << "KexiBoolTableEdit: m_origValue.typeName()==" << m_origValue.typeName() << endl; + kdDebug() << "KexiBoolTableEdit: type== " << field()->typeName() << endl; + m_hasFocusableWidget = false; + m_acceptEditorAfterDeleteContents = true; + m_usesSelectedTextColor = false; +} + +KexiBoolTableEdit::~KexiBoolTableEdit() +{ +} + +void KexiBoolTableEdit::setValueInternal(const QVariant& /*add*/, bool /*removeOld*/) +{ + m_currentValue = m_origValue; + //nothing to do more... +} + +bool KexiBoolTableEdit::valueIsNull() +{ + return m_currentValue.isNull(); +} + +bool KexiBoolTableEdit::valueIsEmpty() +{ + return m_currentValue.isNull(); +} + +QVariant KexiBoolTableEdit::value() +{ +// ok = true; + return m_currentValue; +} + +void KexiBoolTableEdit::clear() +{ + if (field()->isNotNull()) + m_currentValue = QVariant(false, 0); + else + m_currentValue = QVariant(); +} + +bool KexiBoolTableEdit::cursorAtStart() +{ + return true; +} + +bool KexiBoolTableEdit::cursorAtEnd() +{ + return true; +} + +void KexiBoolTableEdit::setupContents( QPainter *p, bool focused, const QVariant& val, + QString &txt, int &align, int &x, int &y_offset, int &w, int &h ) +{ + Q_UNUSED(focused); + Q_UNUSED(txt); + Q_UNUSED(align); + Q_UNUSED(x); +#ifdef Q_WS_WIN +// x = 1; + y_offset = -1; +#else +// x = 1; + y_offset = 0; +#endif + if (p) { + int s = QMAX(h - 5, 12); + s = QMIN( h-3, s ); + s = QMIN( w-3, s );//avoid too large box + QRect r( QMAX( w/2 - s/2, 0 ) , h/2 - s/2 /*- 1*/, s, s); +//already set ouotside: p->setPen(QPen(colorGroup().text(), 1)); + p->drawRect(r); + if (val.isNull()) { // && !field()->isNotNull()) { + p->drawText( r, Qt::AlignCenter, "?" ); + } + else if (val.toBool()) { + p->drawLine(r.x(), r.y(), r.right(), r.bottom()); + p->drawLine(r.x(), r.bottom(), r.right(), r.y()); + } + } +} + +void KexiBoolTableEdit::clickedOnContents() +{ + if (field()->isNotNull()) + m_currentValue = QVariant( !m_currentValue.toBool(), 0 ); + else { + // null allowed: use the cycle: true -> false -> null + if (m_currentValue.isNull()) + m_currentValue = QVariant( true, 1 ); + else + m_currentValue = m_currentValue.toBool() ? QVariant( false, 1 ) : QVariant(); + } +} + +void KexiBoolTableEdit::handleAction(const QString& actionName) +{ + if (actionName=="edit_paste") { + emit editRequested(); + bool ok; + const int value = qApp->clipboard()->text( QClipboard::Clipboard ).toInt(&ok); + if (ok) { + m_currentValue = (value==0) ? QVariant(false, 0) : QVariant(true, 1); + } + else { + m_currentValue = field()->isNotNull() + ? QVariant(0, false)/*0 instead of NULL - handle case when null is not allowed*/ + : QVariant(); + } + repaintRelatedCell(); + } + else if (actionName=="edit_cut") { + emit editRequested(); +//! @todo handle defaultValue... + m_currentValue = field()->isNotNull() + ? QVariant(0, false)/*0 instead of NULL - handle case when null is not allowed*/ + : QVariant(); + handleCopyAction(m_origValue, QVariant()); + repaintRelatedCell(); + } +} + +void KexiBoolTableEdit::handleCopyAction(const QVariant& value, const QVariant& visibleValue) +{ + Q_UNUSED(visibleValue); + if (value.type()==QVariant::Bool) + qApp->clipboard()->setText(value.toBool() ? "1" : "0"); + else + qApp->clipboard()->setText(QString::null); +} + +int KexiBoolTableEdit::widthForValue( QVariant &val, const QFontMetrics &fm ) +{ + Q_UNUSED(fm); + return val.toPixmap().width(); +} + +//====================================================== + +KEXI_CELLEDITOR_FACTORY_ITEM_IMPL(KexiBoolEditorFactoryItem, KexiBoolTableEdit) + +#include "kexibooltableedit.moc" + diff --git a/kexi/widget/tableview/kexibooltableedit.h b/kexi/widget/tableview/kexibooltableedit.h new file mode 100644 index 00000000..3320a573 --- /dev/null +++ b/kexi/widget/tableview/kexibooltableedit.h @@ -0,0 +1,87 @@ +/* This file is part of the KDE project + Copyright (C) 2004 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. + */ + +#ifndef KEXIBOOLTABLEEDIT_H +#define KEXIBOOLTABLEEDIT_H + +#include <qvariant.h> + +#include "kexitableedit.h" +#include "kexicelleditorfactory.h" + +/*! @short Cell editor for boolean type. +*/ +class KexiBoolTableEdit : public KexiTableEdit +{ + Q_OBJECT + + public: + KexiBoolTableEdit(KexiTableViewColumn &column, QWidget *parent=0); + + virtual ~KexiBoolTableEdit(); + + //! \return true if editor's value is null (not empty) + virtual bool valueIsNull(); + + //! \return true if editor's value is empty (not null). + //! Only few field types can accept "EMPTY" property + //! (check this with KexiDB::Field::hasEmptyProperty()), + virtual bool valueIsEmpty(); + + virtual QVariant value(); + + virtual bool cursorAtStart(); + virtual bool cursorAtEnd(); + + virtual void clear(); + + virtual void setupContents( QPainter *p, bool focused, const QVariant& val, + QString &txt, int &align, int &x, int &y_offset, int &w, int &h ); + + virtual void clickedOnContents(); + + /*! Handles action having standard name \a actionName. + Action could be: "edit_cut", "edit_paste", etc. */ + virtual void handleAction(const QString& actionName); + + /*! Handles copy action for value. Copies empty string for null, "1" for true, "0" for false. + \a visibleValue is unused here. Reimplemented after KexiTableEdit. */ + virtual void handleCopyAction(const QVariant& value, const QVariant& visibleValue); + + /*! \return width of \a value. Reimplemented after KexiTableEdit. */ + virtual int widthForValue( QVariant &val, const QFontMetrics &fm ); + + protected slots: + + protected: + //! initializes this editor with \a add value + virtual void setValueInternal(const QVariant& add, bool removeOld); + + void showHintButton(); + + //! We've no editor widget that would store current value, so we do this here + QVariant m_currentValue; + + signals: + void hintClicked(); +}; + +KEXI_DECLARE_CELLEDITOR_FACTORY_ITEM(KexiBoolEditorFactoryItem) + +#endif diff --git a/kexi/widget/tableview/kexicelleditorfactory.cpp b/kexi/widget/tableview/kexicelleditorfactory.cpp new file mode 100644 index 00000000..a20eac07 --- /dev/null +++ b/kexi/widget/tableview/kexicelleditorfactory.cpp @@ -0,0 +1,198 @@ +/* This file is part of the KDE project + Copyright (C) 2004-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 "kexicelleditorfactory.h" + +#include <qptrdict.h> +#include <qintdict.h> +#include <kstaticdeleter.h> + +#include <kexidb/indexschema.h> +#include <kexidb/tableschema.h> +#include "kexitableviewdata.h" +#include "kexidatetableedit.h" +#include "kexitimetableedit.h" +#include "kexidatetimetableedit.h" +#include "kexitableedit.h" +#include "kexiinputtableedit.h" +#include "kexicomboboxtableedit.h" +#include "kexiblobtableedit.h" +#include "kexibooltableedit.h" + +//============= KexiCellEditorFactoryItem ============ + +KexiCellEditorFactoryItem::KexiCellEditorFactoryItem() +{ +} + +KexiCellEditorFactoryItem::~KexiCellEditorFactoryItem() +{ +} + +//============= KexiCellEditorFactoryPrivate ============ + +//! @internal +class KexiCellEditorFactoryPrivate +{ + public: + KexiCellEditorFactoryPrivate() + : items(101) + , items_by_type(101, false) + { + items.setAutoDelete( true ); + items_by_type.setAutoDelete( false ); + } + ~KexiCellEditorFactoryPrivate() {} + + QString key(uint type, const QString& subType) const + { + QString key = QString::number(type); + if (!subType.isEmpty()) + key += (QString(" ") + subType); + return key; + } + + void registerItem( KexiCellEditorFactoryItem& item, uint type, const QString& subType = QString::null ) + { + if (!items[ &item ]) + items.insert( &item, &item ); + + items_by_type.insert( key(type, subType), &item ); + } + + KexiCellEditorFactoryItem *findItem(uint type, const QString& subType) + { + KexiCellEditorFactoryItem *item = items_by_type[ key(type, subType) ]; + if (item) + return item; + item = items_by_type[ key(type, QString::null) ]; + if (item) + return item; + return items_by_type[ key( KexiDB::Field::InvalidType, QString::null ) ]; + } + + QPtrDict<KexiCellEditorFactoryItem> items; //!< list of editor factory items (for later destroy) + + QDict<KexiCellEditorFactoryItem> items_by_type; //!< editor factory items accessed by a key +}; + +static KStaticDeleter<KexiCellEditorFactoryPrivate> KexiCellEditorFactory_deleter; +static KexiCellEditorFactoryPrivate *KexiCellEditorFactory_static = 0; + +//============= KexiCellEditorFactory ============ + +KexiCellEditorFactory::KexiCellEditorFactory() +{ +} + +KexiCellEditorFactory::~KexiCellEditorFactory() +{ +} + + +// Initializes standard editor cell editor factories +void KexiCellEditorFactory::init() +{ + if (KexiCellEditorFactory_static) + return; + KexiCellEditorFactory_deleter.setObject(KexiCellEditorFactory_static, new KexiCellEditorFactoryPrivate()); + + KexiCellEditorFactory_static->registerItem( *new KexiBlobEditorFactoryItem(), KexiDB::Field::BLOB ); + KexiCellEditorFactory_static->registerItem( *new KexiDateEditorFactoryItem(), KexiDB::Field::Date ); + KexiCellEditorFactory_static->registerItem( *new KexiTimeEditorFactoryItem(), KexiDB::Field::Time ); + KexiCellEditorFactory_static->registerItem( *new KexiDateTimeEditorFactoryItem(), KexiDB::Field::DateTime ); + KexiCellEditorFactory_static->registerItem( *new KexiComboBoxEditorFactoryItem(), KexiDB::Field::Enum ); + KexiCellEditorFactory_static->registerItem( *new KexiBoolEditorFactoryItem(), KexiDB::Field::Boolean ); + KexiCellEditorFactory_static->registerItem( *new KexiKIconTableEditorFactoryItem(), KexiDB::Field::Text, "KIcon" ); + //default type + KexiCellEditorFactory_static->registerItem( *new KexiInputEditorFactoryItem(), KexiDB::Field::InvalidType ); +} + +void KexiCellEditorFactory::registerItem( KexiCellEditorFactoryItem& item, uint type, const QString& subType ) +{ + init(); + KexiCellEditorFactory_static->registerItem( item, type, subType ); +} + +static bool hasEnumType( const KexiTableViewColumn &column ) +{ + /*not db-aware case*/ + if (column.relatedData()) + return true; + /*db-aware case*/ + if (!column.field() || !column.field()->table()) + return false; + KexiDB::LookupFieldSchema *lookupFieldSchema = column.field()->table()->lookupFieldSchema( *column.field() ); + if (!lookupFieldSchema) + return false; + if (lookupFieldSchema->rowSource().name().isEmpty()) + return false; + return true; +} + +KexiTableEdit* KexiCellEditorFactory::createEditor(KexiTableViewColumn &column, QWidget* parent) +{ + init(); + KexiDB::Field *realField; + if (column.visibleLookupColumnInfo) { + realField = column.visibleLookupColumnInfo->field; + } + else { + realField = column.field(); + } + + KexiCellEditorFactoryItem *item = 0; + + if (hasEnumType(column)) { + //--we need to create combo box because of relationship: + item = KexiCellEditorFactory::item( KexiDB::Field::Enum ); + } + else { + item = KexiCellEditorFactory::item( realField->type(), realField->subType() ); + } + +#if 0 //js: TODO LATER + //--check if we need to create combo box because of relationship: + //WARNING: it's assumed that indices are one-field long + KexiDB::TableSchema *table = f.table(); + if (table) { + //find index that contain this field + KexiDB::IndexSchema::ListIterator it = table->indicesIterator(); + for (;it.current();++it) { + KexiDB::IndexSchema *idx = it.current(); + if (idx->fields()->findRef(&f)!=-1) { + //find details-side rel. for this index + KexiDB::Relationship *rel = idx->detailsRelationships()->first(); + if (rel) { + + } + } + } + } +#endif + + return item->createEditor(column, parent); +} + +KexiCellEditorFactoryItem* KexiCellEditorFactory::item( uint type, const QString& subType ) +{ + init(); + return KexiCellEditorFactory_static->findItem(type, subType); +} + diff --git a/kexi/widget/tableview/kexicelleditorfactory.h b/kexi/widget/tableview/kexicelleditorfactory.h new file mode 100644 index 00000000..1b68cb8d --- /dev/null +++ b/kexi/widget/tableview/kexicelleditorfactory.h @@ -0,0 +1,79 @@ +/* This file is part of the KDE project + Copyright (C) 2004-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. +*/ + +#ifndef KEXICELLEDITORFACTORY_H +#define KEXICELLEDITORFACTORY_H + +#include <qvariant.h> +#include <qwidget.h> + +#include <kexidb/field.h> + +class KexiCellEditorFactoryItem; +class KexiTableEdit; +class KexiTableViewColumn; + +//! A singleton class providing access to cell editor factories +class KEXIDATATABLE_EXPORT KexiCellEditorFactory +{ + public: + KexiCellEditorFactory(); + virtual ~KexiCellEditorFactory(); + + /*! Registers factory item for \a type and (optional) \a subType. + \a subType is usually obtained (e.g. in KexiTableView) from KexiDB::Field::subType(). + Passing KexiDB::Field::Invalid as type will set default item, + i.e. the one that will be used when no other item is defined for given data type. + You can register the same \a item many times for different types and subtypes. + Once registered, \a item object will be owned by the factory, so you shouldn't + care about deleting it. */ + static void registerItem( KexiCellEditorFactoryItem& item, uint type, + const QString& subType = QString::null ); + + /*! \return item for \a type and (optional) \a subType. + If no item found, the one with empty subtype is tried. + If still no item found, the default is tried. Eventually, may return NULL. */ + static KexiCellEditorFactoryItem* item( uint type, const QString& subType = QString::null ); + +// static KexiTableEdit* createEditor(KexiDB::Field &f, QScrollView* parent = 0); + /*! Creates a new editor for \a column. If \a parent is of QScrollView, the new editor + will be created inside parent->viewport() instead. */ + static KexiTableEdit* createEditor(KexiTableViewColumn &column, QWidget* parent = 0); + + protected: + static void init(); +}; + +//! A base class for implementing cell editor factories +class KEXIDATATABLE_EXPORT KexiCellEditorFactoryItem +{ + public: + KexiCellEditorFactoryItem(); + virtual ~KexiCellEditorFactoryItem(); + QString className() { return m_className; } + + protected: +// virtual KexiTableEdit* createEditor(KexiDB::Field &f, QScrollView* parent = 0) = 0; + virtual KexiTableEdit* createEditor(KexiTableViewColumn &column, QWidget* parent = 0) = 0; + + QString m_className; + friend class KexiCellEditorFactory; +}; + +#endif diff --git a/kexi/widget/tableview/kexicomboboxbase.cpp b/kexi/widget/tableview/kexicomboboxbase.cpp new file mode 100644 index 00000000..2d6d52ac --- /dev/null +++ b/kexi/widget/tableview/kexicomboboxbase.cpp @@ -0,0 +1,597 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Peter Simonsson <psn@linux.se> + Copyright (C) 2003-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 <qlayout.h> +#include <qstyle.h> +#include <qwindowsstyle.h> +#include <qpainter.h> + +#include "kexicomboboxbase.h" +#include <widget/utils/kexicomboboxdropdownbutton.h> +#include "kexicomboboxpopup.h" +#include "kexitableview.h" +#include "kexitableitem.h" +#include "kexi.h" + +#include <klineedit.h> + +KexiComboBoxBase::KexiComboBoxBase() +{ + m_internalEditorValueChanged = false; //user has text or other value inside editor + m_slotInternalEditorValueChanged_enabled = true; + m_mouseBtnPressedWhenPopupVisible = false; + m_insideCreatePopup = false; + m_setValueOrTextInInternalEditor_enabled = true; + m_updatePopupSelectionOnShow = true; + m_moveCursorToEndInInternalEditor_enabled = true; + m_selectAllInInternalEditor_enabled = true; + m_setValueInInternalEditor_enabled = true; + m_setVisibleValueOnSetValueInternal = false; +} + +KexiComboBoxBase::~KexiComboBoxBase() +{ +} + +KexiDB::LookupFieldSchema *KexiComboBoxBase::lookupFieldSchema() const +{ + if (field() && field()->table()) { + KexiDB::LookupFieldSchema *lookupFieldSchema = field()->table()->lookupFieldSchema( *field() ); + if (lookupFieldSchema && !lookupFieldSchema->rowSource().name().isEmpty()) + return lookupFieldSchema; + } + return 0; +} + +int KexiComboBoxBase::rowToHighlightForLookupTable() const +{ + if (!popup()) + return -1;//err + KexiDB::LookupFieldSchema *lookupFieldSchema = this->lookupFieldSchema(); + if (!lookupFieldSchema) + return -1; + if (lookupFieldSchema->boundColumn()==-1) + return -1; //err + bool ok; + const int rowUid = origValue().toInt(); +//! @todo for now we're assuming the id is INTEGER + KexiTableViewData *tvData = popup()->tableView()->data(); + const int boundColumn = lookupFieldSchema->boundColumn(); + KexiTableViewData::Iterator it(tvData->iterator()); + int row=0; + for (;it.current();++it, row++) + { + if (it.current()->at(boundColumn).toInt(&ok) == rowUid && ok || !ok) + break; + } + if (!ok || !it.current()) //item not found: highlight 1st row, if available + return -1; + return row; +} + +void KexiComboBoxBase::setValueInternal(const QVariant& add_, bool removeOld) +{ + Q_UNUSED(removeOld); + m_mouseBtnPressedWhenPopupVisible = false; + m_updatePopupSelectionOnShow = true; + QString add(add_.toString()); + if (add.isEmpty()) { + KexiTableViewData *relData = column() ? column()->relatedData() : 0; + QVariant valueToSet; + bool hasValueToSet = true; + int rowToHighlight = -1; + KexiDB::LookupFieldSchema *lookupFieldSchema = this->lookupFieldSchema(); + if (lookupFieldSchema) { + //use 'lookup field' model +//! @todo support more RowSourceType's, not only table + if (lookupFieldSchema->boundColumn()==-1) +//! @todo errmsg + return; + if (m_setVisibleValueOnSetValueInternal) { + //only for table views + if (!popup()) + createPopup(false/*!show*/); + } + if (popup()) { + const int rowToHighlight = rowToHighlightForLookupTable(); + popup()->tableView()->setHighlightedRow(rowToHighlight); + + const int visibleColumn = lookupFieldSchema->visibleColumn( popup()->tableView()->data()->columnsCount() ); + if (m_setVisibleValueOnSetValueInternal && -1!=visibleColumn) { + //only for table views + KexiTableItem *it = popup()->tableView()->highlightedItem(); + if (it) + valueToSet = it->at( visibleColumn ); + } + else { + hasValueToSet = false; + } + } + } + else if (relData) { + //use 'related table data' model + valueToSet = valueForString(origValue().toString(), &rowToHighlight, 0, 1); + } + else { + //use 'enum hints' model + const int row = origValue().toInt(); + valueToSet = field()->enumHint(row).stripWhiteSpace(); + } + if (hasValueToSet) + setValueOrTextInInternalEditor( valueToSet ); + /*impl.*/moveCursorToEndInInternalEditor(); + /*impl.*/selectAllInInternalEditor(); + + if (popup()) { + if (origValue().isNull()) { + popup()->tableView()->clearSelection(); + popup()->tableView()->setHighlightedRow(0); + } else { + if (relData) { + if (rowToHighlight!=-1) + popup()->tableView()->setHighlightedRow(rowToHighlight); + } + else if (!lookupFieldSchema) { + //popup()->tableView()->selectRow(origValue().toInt()); + popup()->tableView()->setHighlightedRow(origValue().toInt()); + } + } + } + } + else { + //todo: autocompl.? + if (popup()) + popup()->tableView()->clearSelection(); + /*impl.*/setValueInInternalEditor(add); //not setLineEditText(), because 'add' is entered by user! + //setLineEditText( add ); + /*impl.*/moveCursorToEndInInternalEditor(); + } +} + +KexiTableItem* KexiComboBoxBase::selectItemForEnteredValueInLookupTable(const QVariant& v) +{ + KexiDB::LookupFieldSchema *lookupFieldSchema = this->lookupFieldSchema(); + if (!popup() || !lookupFieldSchema) + return 0; //safety +//-not effective for large sets: please cache it! +//.stripWhiteSpace() is not generic! + + const bool valueIsText = v.type()==QVariant::String || v.type()==QVariant::CString; //most common case + const QString txt( valueIsText ? v.toString().stripWhiteSpace().lower() : QString::null ); + KexiTableViewData *lookupData = popup()->tableView()->data(); + const int visibleColumn = lookupFieldSchema->visibleColumn( lookupData->columnsCount() ); + if (-1 == visibleColumn) + return 0; + KexiTableViewData::Iterator it(lookupData->iterator()); + int row; + for (row = 0;it.current();++it, row++) { + if (valueIsText) { + if (it.current()->at(visibleColumn).toString().stripWhiteSpace().lower() == txt) + break; + } + else { + if (it.current()->at(visibleColumn) == v) + break; + } + } + + m_setValueOrTextInInternalEditor_enabled = false; // <-- this is the entered value, + // so do not change the internal editor's contents + if (it.current()) + popup()->tableView()->selectRow(row); + else + popup()->tableView()->clearSelection(); + + m_setValueOrTextInInternalEditor_enabled = true; + + return it.current(); +} + +QString KexiComboBoxBase::valueForString(const QString& str, int* row, + uint lookInColumn, uint returnFromColumn, bool allowNulls) +{ + KexiTableViewData *relData = column() ? column()->relatedData() : 0; + if (!relData) + return QString::null; //safety + //use 'related table data' model + //-not effective for large sets: please cache it! + //.stripWhiteSpace() is not generic! + + const QString txt = str.stripWhiteSpace().lower(); + KexiTableViewData::Iterator it( relData->iterator() ); + for (*row = 0;it.current();++it, (*row)++) { + if (it.current()->at(lookInColumn).toString().stripWhiteSpace().lower()==txt) + break; + } + if (it.current()) + return it.current()->at(returnFromColumn).toString(); + + *row = -1; + + if (column() && column()->relatedDataEditable()) + return str; //new value entered and that's allowed + + kexiwarn << "KexiComboBoxBase::valueForString(): no related row found, ID will be painted!" << endl; + if (allowNulls) + return QString::null; + return str; //for sanity but it's weird to show id to the user +} + +QVariant KexiComboBoxBase::value() +{ + KexiTableViewData *relData = column() ? column()->relatedData() : 0; + KexiDB::LookupFieldSchema *lookupFieldSchema = 0; + if (relData) { + if (m_internalEditorValueChanged) { + //we've user-entered text: look for id +//TODO: make error if matching text not found? + int rowToHighlight; + return valueForString(m_userEnteredValue.toString(), &rowToHighlight, 1, 0, true/*allowNulls*/); + } + else { + //use 'related table data' model + KexiTableItem *it = popup() ? popup()->tableView()->selectedItem() : 0; + return it ? it->at(0) : origValue();//QVariant(); + } + } + else if ((lookupFieldSchema = this->lookupFieldSchema())) + { + if (lookupFieldSchema->boundColumn()==-1) + return origValue(); + KexiTableItem *it = popup() ? popup()->tableView()->selectedItem() : 0; + if (/*!it &&*/ m_internalEditorValueChanged && !m_userEnteredValue.toString().isEmpty()) { // + //try to select a row using the user-entered text + if (!popup()) { + QVariant prevUserEnteredValue = m_userEnteredValue; + createPopup(false); + m_userEnteredValue = prevUserEnteredValue; + } + it = selectItemForEnteredValueInLookupTable( m_userEnteredValue ); + } + return it ? it->at( lookupFieldSchema->boundColumn() ) : QVariant(); + } + else if (popup()) { + //use 'enum hints' model + const int row = popup()->tableView()->currentRow(); + if (row>=0) + return QVariant( row ); + } + + if (valueFromInternalEditor().toString().isEmpty()) + return QVariant(); +/*! \todo don't return just 1st row, but use autocompletion feature + and: show message box if entered text does not match! */ + return origValue(); //unchanged +} + +QVariant KexiComboBoxBase::visibleValueForLookupField() +{ + KexiDB::LookupFieldSchema *lookupFieldSchema = this->lookupFieldSchema(); + if (!popup() || !lookupFieldSchema) + return QVariant(); + const int visibleColumn = lookupFieldSchema->visibleColumn( popup()->tableView()->data()->columnsCount() ); + if (-1 == visibleColumn) + return QVariant(); + KexiTableItem *it = popup()->tableView()->selectedItem(); + return it ? it->at( QMIN( (uint)visibleColumn, it->count()-1)/*sanity*/ ) : QVariant(); +} + +QVariant KexiComboBoxBase::visibleValue() +{ + return m_visibleValue; +} + +void KexiComboBoxBase::clear() +{ + if (popup()) + popup()->hide(); + slotInternalEditorValueChanged(QVariant()); +} + +tristate KexiComboBoxBase::valueChangedInternal() +{ + //avoid comparing values: + KexiTableViewData *relData = column() ? column()->relatedData() : 0; + KexiDB::LookupFieldSchema *lookupFieldSchema = this->lookupFieldSchema(); + if (relData || lookupFieldSchema) { + if (m_internalEditorValueChanged) + return true; + + //use 'related table data' model + KexiTableItem *it = popup() ? popup()->tableView()->selectedItem() : 0; + if (!it) + return false; + } + else { + //use 'enum hints' model + const int row = popup() ? popup()->tableView()->currentRow() : -1; + if (row<0 && !m_internalEditorValueChanged/*true if text box is cleared*/) + return false; + } + + return cancelled; +} + +bool KexiComboBoxBase::valueIsNull() +{ +// bool ok; + QVariant v( value() ); + return v.isNull(); +// return !ok || v.isNull(); +} + +bool KexiComboBoxBase::valueIsEmpty() +{ + return valueIsNull(); +} + +void KexiComboBoxBase::showPopup() +{ + createPopup(true); +} + +void KexiComboBoxBase::createPopup(bool show) +{ + if (!field()) + return; + m_insideCreatePopup = true; + QWidget* thisWidget = dynamic_cast<QWidget*>(this); + QWidget *widgetToFocus = internalEditor() ? internalEditor() : thisWidget; + if (!popup()) { + setPopup( column() ? new KexiComboBoxPopup(thisWidget, *column()) + : new KexiComboBoxPopup(thisWidget, *field()) ); + QObject::connect(popup(), SIGNAL(rowAccepted(KexiTableItem*,int)), + thisWidget, SLOT(slotRowAccepted(KexiTableItem*,int))); + QObject::connect(popup()->tableView(), SIGNAL(itemSelected(KexiTableItem*)), + thisWidget, SLOT(slotItemSelected(KexiTableItem*))); + + popup()->setFocusProxy( widgetToFocus ); + popup()->tableView()->setFocusProxy( widgetToFocus ); + popup()->installEventFilter(thisWidget); + + if (origValue().isNull()) + popup()->tableView()->clearSelection(); + else { + popup()->tableView()->selectRow( 0 ); + popup()->tableView()->setHighlightedRow( 0 ); + } + } + if (show && internalEditor() && !internalEditor()->isVisible()) + /*emit*/editRequested(); + + QPoint posMappedToGlobal = mapFromParentToGlobal(thisWidget->pos()); + if (posMappedToGlobal != QPoint(-1,-1)) { +//! todo alter the position to fit the popup within screen boundaries + popup()->move( posMappedToGlobal + QPoint(0, thisWidget->height()) ); + //to avoid flickering: first resize to 0-height, then show and resize back to prev. height + const int w = popupWidthHint(); + popup()->resize(w, 0); + if (show) + popup()->show(); + popup()->updateSize(w); + if (m_updatePopupSelectionOnShow) { + int rowToHighlight = -1; + KexiDB::LookupFieldSchema *lookupFieldSchema = this->lookupFieldSchema(); + KexiTableViewData *relData = column() ? column()->relatedData() : 0; + if (lookupFieldSchema) { + rowToHighlight = rowToHighlightForLookupTable(); + } + else if (relData) { + (void)valueForString(origValue().toString(), &rowToHighlight, 0, 1); + } + else //enum hint + rowToHighlight = origValue().toInt(); + +/*-->*/ m_moveCursorToEndInInternalEditor_enabled = show; + m_selectAllInInternalEditor_enabled = show; + m_setValueInInternalEditor_enabled = show; + if (rowToHighlight==-1) { + rowToHighlight = QMAX( popup()->tableView()->highlightedRow(), 0); + setValueInInternalEditor(QVariant()); + } + popup()->tableView()->selectRow( rowToHighlight ); + popup()->tableView()->setHighlightedRow( rowToHighlight ); + if (rowToHighlight < popup()->tableView()->rowsPerPage()) + popup()->tableView()->ensureCellVisible( 0, -1 ); + +/*-->*/ m_moveCursorToEndInInternalEditor_enabled = true; + m_selectAllInInternalEditor_enabled = true; + m_setValueInInternalEditor_enabled = true; + } + } + + if (show) { + moveCursorToEndInInternalEditor(); + selectAllInInternalEditor(); + widgetToFocus->setFocus(); + } + m_insideCreatePopup = false; +} + +void KexiComboBoxBase::hide() +{ + if (popup()) + popup()->hide(); +} + +void KexiComboBoxBase::slotRowAccepted(KexiTableItem * item, int row) +{ + Q_UNUSED(row); + //update our value + //..nothing to do? + updateButton(); + slotItemSelected(item); + /*emit*/acceptRequested(); +} + +void KexiComboBoxBase::acceptPopupSelection() +{ + if (!popup()) + return; + KexiTableItem *item = popup()->tableView()->highlightedItem(); + if (item) { + popup()->tableView()->selectRow( popup()->tableView()->highlightedRow() ); + slotRowAccepted(item, -1); + } + popup()->hide(); +} + +void KexiComboBoxBase::slotItemSelected(KexiTableItem*) +{ + kexidbg << "KexiComboBoxBase::slotItemSelected(): m_visibleValue = " << m_visibleValue << endl; + + QVariant valueToSet; + KexiTableViewData *relData = column() ? column()->relatedData() : 0; + KexiDB::LookupFieldSchema *lookupFieldSchema = this->lookupFieldSchema(); + + m_visibleValue = lookupFieldSchema ? visibleValueForLookupField() : QVariant(); + + if (relData) { + //use 'related table data' model + KexiTableItem *item = popup()->tableView()->selectedItem(); + if (item) + valueToSet = item->at(1); + } + else if (lookupFieldSchema) { + KexiTableItem *item = popup()->tableView()->selectedItem(); + const int visibleColumn = lookupFieldSchema->visibleColumn( popup()->tableView()->data()->columnsCount() ); + if (item && visibleColumn!=-1 /* && (int)item->size() >= visibleColumn --already checked*/) { + valueToSet = item->at( QMIN( (uint)visibleColumn, item->count()-1)/*sanity*/ ); + } + } + else { + //use 'enum hints' model + valueToSet = field()->enumHint( popup()->tableView()->currentRow() ); + if (valueToSet.toString().isEmpty() && !m_insideCreatePopup) { + clear(); + QWidget* thisWidget = dynamic_cast<QWidget*>(this); + thisWidget->parentWidget()->setFocus(); + return; + } + } + setValueOrTextInInternalEditor( valueToSet ); + if (m_setValueOrTextInInternalEditor_enabled) { + moveCursorToEndInInternalEditor(); + selectAllInInternalEditor(); + } + // a new (temp) popup table index is selected: do not update selection next time: + m_updatePopupSelectionOnShow = false; +} + +void KexiComboBoxBase::slotInternalEditorValueChanged(const QVariant& v) +{ + if (!m_slotInternalEditorValueChanged_enabled) + return; + m_userEnteredValue = v; + m_internalEditorValueChanged = true; + if (v.toString().isEmpty()) { + if (popup()) { + popup()->tableView()->clearSelection(); + } + return; + } +} + +void KexiComboBoxBase::setValueOrTextInInternalEditor(const QVariant& value) +{ + if (!m_setValueOrTextInInternalEditor_enabled) + return; + setValueInInternalEditor( value ); + //this text is not entered by hand: + m_userEnteredValue = QVariant(); + m_internalEditorValueChanged = false; +} + +bool KexiComboBoxBase::handleKeyPressForPopup( QKeyEvent *ke ) +{ + const int k = ke->key(); + int highlightedOrSelectedRow = popup() ? popup()->tableView()->highlightedRow() : -1; + if (popup() && highlightedOrSelectedRow < 0) + highlightedOrSelectedRow = popup()->tableView()->currentRow(); + + const bool enterPressed = k==Qt::Key_Enter || k==Qt::Key_Return; + + // The editor may be active but the pull down menu not existant/visible, + // e.g. when the user has pressed a normal button to activate the editor + // Don't handle the event here in that case. + if (!popup() || (!enterPressed && !popup()->isVisible())) { + return false; + } + + switch (k) { + case Qt::Key_Up: + popup()->tableView()->setHighlightedRow( + QMAX(highlightedOrSelectedRow-1, 0) ); + updateTextForHighlightedRow(); + return true; + case Qt::Key_Down: + popup()->tableView()->setHighlightedRow( + QMIN(highlightedOrSelectedRow+1, popup()->tableView()->rows()-1) ); + updateTextForHighlightedRow(); + return true; + case Qt::Key_PageUp: + popup()->tableView()->setHighlightedRow( + QMAX(highlightedOrSelectedRow-popup()->tableView()->rowsPerPage(), 0) ); + updateTextForHighlightedRow(); + return true; + case Qt::Key_PageDown: + popup()->tableView()->setHighlightedRow( + QMIN(highlightedOrSelectedRow+popup()->tableView()->rowsPerPage(), + popup()->tableView()->rows()-1) ); + updateTextForHighlightedRow(); + return true; + case Qt::Key_Home: + popup()->tableView()->setHighlightedRow( 0 ); + updateTextForHighlightedRow(); + return true; + case Qt::Key_End: + popup()->tableView()->setHighlightedRow( popup()->tableView()->rows()-1 ); + updateTextForHighlightedRow(); + return true; + case Qt::Key_Enter: + case Qt::Key_Return: //accept + //select row that is highlighted + if (popup()->tableView()->highlightedRow()>=0) + popup()->tableView()->selectRow( popup()->tableView()->highlightedRow() ); + //do not return true: allow to process event + default: ; + } + return false; +} + +void KexiComboBoxBase::updateTextForHighlightedRow() +{ + KexiTableItem *item = popup() ? popup()->tableView()->highlightedItem() : 0; + if (item) + slotItemSelected(item); +} + +void KexiComboBoxBase::undoChanges() +{ + KexiDB::LookupFieldSchema *lookupFieldSchema = this->lookupFieldSchema(); + if (lookupFieldSchema) { +// kexidbg << "KexiComboBoxBase::undoChanges(): m_visibleValue BEFORE = " << m_visibleValue << endl; + if (popup()) + popup()->tableView()->selectRow( popup()->tableView()->highlightedRow() ); + m_visibleValue = visibleValueForLookupField(); +// kexidbg << "KexiComboBoxBase::undoChanges(): m_visibleValue AFTER = " << m_visibleValue << endl; + setValueOrTextInInternalEditor( m_visibleValue ); + } +} diff --git a/kexi/widget/tableview/kexicomboboxbase.h b/kexi/widget/tableview/kexicomboboxbase.h new file mode 100644 index 00000000..1433ab0f --- /dev/null +++ b/kexi/widget/tableview/kexicomboboxbase.h @@ -0,0 +1,170 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Peter Simonsson <psn@linux.se> + Copyright (C) 2003-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. + */ + +#ifndef _KEXICOMBOBOXBASE_H_ +#define _KEXICOMBOBOXBASE_H_ + +#include "kexidb/field.h" +#include "kexiinputtableedit.h" +#include <kexidb/lookupfieldschema.h> + +class KPushButton; +class KLineEdit; +class KexiComboBoxPopup; +class KexiTableItem; +class KexiTableViewColumn; + +/*! @short A base class for handling data-aware combo boxes. + This class is used by KexiComboBoxTableEdit and KexiDBComboBox. +*/ +class KEXIDATATABLE_EXPORT KexiComboBoxBase +{ + public: + KexiComboBoxBase(); + virtual ~KexiComboBoxBase(); + + //! \return column related to this combo; for KexiComboBoxTableEdit 0 is returned here + virtual KexiTableViewColumn *column() const = 0; + + //! \return database field related to this combo + virtual KexiDB::Field *field() const = 0; + + //! \return the original value + virtual QVariant origValue() const = 0; + + //! Note: Generally in current implementation this is integer > 0; may be null if no value is set + virtual QVariant value(); + + virtual QVariant visibleValue(); + + //! Reimplement this and call this impl.: used to clear internal editor + virtual void clear(); + + virtual tristate valueChangedInternal(); + virtual bool valueIsNull(); + virtual bool valueIsEmpty(); + + public: + virtual void hide(); + + void createPopup(bool show); + + void showPopup(); + + //! Call this from slot + virtual void slotRowAccepted(KexiTableItem *item, int row); + + //! Call this from slot + virtual void slotItemSelected(KexiTableItem*); + + //! Call this from slot + void slotInternalEditorValueChanged(const QVariant &v); + + //! Implement this to return the internal editor + virtual QWidget *internalEditor() const = 0; + + protected: + virtual void setValueInternal(const QVariant& add, bool removeOld); + + //! Used to select row item for an user-entered value \a v. + //! Only for "lookup table" mode. + KexiTableItem* selectItemForEnteredValueInLookupTable(const QVariant& v); + + /*! \return value from \a returnFromColumn related to \a str value from column \a lookInColumn. + If \a allowNulls is true, NULL is returend if no matched column found, else: + \a str is returned. + Example: lookInColumn=0, returnFromColumn=1 --returns user-visible string + for column #1 for id-column #0 */ + QString valueForString(const QString& str, int* row, uint lookInColumn, + uint returnFromColumn, bool allowNulls = false); + + //! sets \a value for the line edit without setting a flag (m_userEnteredValue) that indicates that + //! the text has been entered by hand (by a user) + void setValueOrTextInInternalEditor(const QVariant& value); //QString& text); + + //! \return lookup field schema for this combo box, if present and if is valid (i.e. has defined row source) + KexiDB::LookupFieldSchema* lookupFieldSchema() const; + + int rowToHighlightForLookupTable() const; + + //! Implement this to perform "move cursor to end" in the internal editor + virtual void moveCursorToEndInInternalEditor() = 0; + + //! Implement this to perform "select all" in the internal editor + virtual void selectAllInInternalEditor() = 0; + + //! Implement this to perform "set value" in the internal editor + virtual void setValueInInternalEditor(const QVariant& value) = 0; + + //! Implement this to return value from the internal editor + virtual QVariant valueFromInternalEditor() = 0; + + //! Implement this as signal + virtual void editRequested() = 0; + + //! Implement this as signal + virtual void acceptRequested() = 0; + + //! Implement this to return a position \a pos mapped from parent (e.g. viewport) + //! to global coordinates. QPoint(-1, -1) should be returned if this cannot be computed. + virtual QPoint mapFromParentToGlobal(const QPoint& pos) const = 0; + + //! Implement this to return a hint for popup width. + virtual int popupWidthHint() const = 0; + + //! Implement this to update button state. Table view just updates on/off state + //! for the button depending on visibility of the popup + virtual void updateButton() {} + + virtual KexiComboBoxPopup *popup() const = 0; + virtual void setPopup(KexiComboBoxPopup *popup) = 0; + + virtual QVariant visibleValueForLookupField(); + + void updateTextForHighlightedRow(); + + bool handleKeyPressForPopup( QKeyEvent *ke ); + + void acceptPopupSelection(); + + //! Used by KexiDBComboBox. + void undoChanges(); + + QVariant m_visibleValue; + + QVariant m_userEnteredValue; //!< value (usually a text) entered by hand (by the user) + + bool m_internalEditorValueChanged : 1; //!< true if user has text or other value inside editor + bool m_slotInternalEditorValueChanged_enabled : 1; //!< Used in slotInternalEditorValueChanged() + bool m_setValueOrTextInInternalEditor_enabled : 1; //!< Used in setValueOrTextInInternalEditor() and slotItemSelected() + bool m_mouseBtnPressedWhenPopupVisible : 1; //!< Used only by KexiComboBoxTableEdit + bool m_insideCreatePopup : 1; //!< true if we're inside createPopup(); used in slotItemSelected() + bool m_updatePopupSelectionOnShow : 1; //!< Set to false as soon as the item corresponding with the current + //!< value is selected in the popup table. This avoids selecting item + //!< for origValue() and thus loosing the recent choice. + bool m_moveCursorToEndInInternalEditor_enabled : 1; + bool m_selectAllInInternalEditor_enabled : 1; + bool m_setValueInInternalEditor_enabled : 1; + bool m_setVisibleValueOnSetValueInternal : 1; //!< Used in setValueInternal() to control whether + //!< we want to set visible value on setValueInternal() + //!< - true for table view's combo box +}; + +#endif diff --git a/kexi/widget/tableview/kexicomboboxpopup.cpp b/kexi/widget/tableview/kexicomboboxpopup.cpp new file mode 100644 index 00000000..5cd65d0d --- /dev/null +++ b/kexi/widget/tableview/kexicomboboxpopup.cpp @@ -0,0 +1,373 @@ +/* This file is part of the KDE project + Copyright (C) 2004-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 "kexicomboboxpopup.h" + +#include "kexidatatableview.h" +#include "kexitableview_p.h" +#include "kexitableitem.h" +#include "kexitableedit.h" + +#include <kexidb/lookupfieldschema.h> +#include <kexidb/expression.h> +#include <kexidb/parser/sqlparser.h> + +#include <kdebug.h> + +#include <qlayout.h> +#include <qevent.h> + +/*! @internal + Helper for KexiComboBoxPopup. */ +class KexiComboBoxPopup_KexiTableView : public KexiDataTableView +{ + public: + KexiComboBoxPopup_KexiTableView(QWidget* parent=0) + : KexiDataTableView(parent, "KexiComboBoxPopup_tv") + { + init(); + } + void init() + { + setReadOnly( true ); + setLineWidth( 0 ); + d->moveCursorOnMouseRelease = true; + KexiTableView::Appearance a(appearance()); + a.navigatorEnabled = false; +//! @todo add option for backgroundAltering?? + a.backgroundAltering = false; + a.fullRowSelection = true; + a.rowHighlightingEnabled = true; + a.rowMouseOverHighlightingEnabled = true; + a.persistentSelections = false; + a.rowMouseOverHighlightingColor = colorGroup().highlight(); + a.rowMouseOverHighlightingTextColor = colorGroup().highlightedText(); + a.rowHighlightingTextColor = a.rowMouseOverHighlightingTextColor; + a.gridEnabled = false; + setAppearance(a); + setInsertingEnabled( false ); + setSortingEnabled( false ); + setVerticalHeaderVisible( false ); + setHorizontalHeaderVisible( false ); + setContextMenuEnabled( false ); + setScrollbarToolTipsEnabled( false ); + installEventFilter(this); + setBottomMarginInternal( - horizontalScrollBar()->sizeHint().height() ); + } + virtual void setData( KexiTableViewData *data, bool owner = true ) + { KexiTableView::setData( data, owner ); } + bool setData(KexiDB::Cursor *cursor) + { return KexiDataTableView::setData( cursor ); } +}; + +//======================================== + +//! @internal +class KexiComboBoxPopupPrivate +{ + public: + KexiComboBoxPopupPrivate() + : int_f(0) + , privateQuery(0) + { + max_rows = KexiComboBoxPopup::defaultMaxRows; + } + ~KexiComboBoxPopupPrivate() { + delete int_f; + delete privateQuery; + } + + KexiComboBoxPopup_KexiTableView *tv; + KexiDB::Field *int_f; //TODO: remove this -temporary + KexiDB::QuerySchema* privateQuery; + int max_rows; +}; + +//======================================== + +const int KexiComboBoxPopup::defaultMaxRows = 8; + +KexiComboBoxPopup::KexiComboBoxPopup(QWidget* parent, KexiTableViewColumn &column) + : QFrame( parent, "KexiComboBoxPopup", WType_Popup ) +{ + init(); + //setup tv data + setData(&column, 0); +} + +KexiComboBoxPopup::KexiComboBoxPopup(QWidget* parent, KexiDB::Field &field) + : QFrame( parent, "KexiComboBoxPopup", WType_Popup ) +{ + init(); + //setup tv data + setData(0, &field); +} + +KexiComboBoxPopup::~KexiComboBoxPopup() +{ + delete d; +} + +void KexiComboBoxPopup::init() +{ + d = new KexiComboBoxPopupPrivate(); + setPaletteBackgroundColor(palette().color(QPalette::Active,QColorGroup::Base)); + setLineWidth( 1 ); + setFrameStyle( Box | Plain ); + + d->tv = new KexiComboBoxPopup_KexiTableView(this); + installEventFilter(this); + + connect(d->tv, SIGNAL(itemReturnPressed(KexiTableItem*,int,int)), + this, SLOT(slotTVItemAccepted(KexiTableItem*,int,int))); + + connect(d->tv, SIGNAL(itemMouseReleased(KexiTableItem*,int,int)), + this, SLOT(slotTVItemAccepted(KexiTableItem*,int,int))); + + connect(d->tv, SIGNAL(itemDblClicked(KexiTableItem*,int,int)), + this, SLOT(slotTVItemAccepted(KexiTableItem*,int,int))); +} + +void KexiComboBoxPopup::setData(KexiTableViewColumn *column, KexiDB::Field *field) +{ + if (column && !field) + field = column->field(); + if (!field) { + kexiwarn << "KexiComboBoxPopup::setData(): !field" << endl; + return; + } + + // case 1: simple related data + if (column && column->relatedData()) { + d->tv->setColumnStretchEnabled( true, -1 ); //only needed when using single column + setDataInternal( column->relatedData(), false /*!owner*/ ); + return; + } + // case 2: lookup field + KexiDB::LookupFieldSchema *lookupFieldSchema = 0; + if (field->table()) + lookupFieldSchema = field->table()->lookupFieldSchema( *field ); + delete d->privateQuery; + d->privateQuery = 0; + if (lookupFieldSchema) { + const QValueList<uint> visibleColumns( lookupFieldSchema->visibleColumns() ); + const bool multipleLookupColumnJoined = visibleColumns.count() > 1; +//! @todo support more RowSourceType's, not only table and query + KexiDB::Cursor *cursor = 0; + switch (lookupFieldSchema->rowSource().type()) { + case KexiDB::LookupFieldSchema::RowSource::Table: { + KexiDB::TableSchema *lookupTable + = field->table()->connection()->tableSchema( lookupFieldSchema->rowSource().name() ); + if (!lookupTable) +//! @todo errmsg + return; + if (multipleLookupColumnJoined) { + kdDebug() << "--- Orig query: " << endl; + lookupTable->query()->debug(); + d->privateQuery = new KexiDB::QuerySchema(*lookupTable->query()); + } + else { + cursor = field->table()->connection()->prepareQuery( *lookupTable ); + } + break; + } + case KexiDB::LookupFieldSchema::RowSource::Query: { + KexiDB::QuerySchema *lookupQuery + = field->table()->connection()->querySchema( lookupFieldSchema->rowSource().name() ); + if (!lookupQuery) +//! @todo errmsg + return; + if (multipleLookupColumnJoined) { + kdDebug() << "--- Orig query: " << endl; + lookupQuery->debug(); + d->privateQuery = new KexiDB::QuerySchema(*lookupQuery); + } + else { + cursor = field->table()->connection()->prepareQuery( *lookupQuery ); + } + break; + } + default:; + } + if (d->privateQuery) { + // append column computed using multiple columns + const KexiDB::QueryColumnInfo::Vector fieldsExpanded( d->privateQuery->fieldsExpanded() ); + uint fieldsExpandedSize( fieldsExpanded.size() ); + KexiDB::BaseExpr *expr = 0; + int count = visibleColumns.count(); + for (QValueList<uint>::ConstIterator it( visibleColumns.at(count-1) ); count>0; count--, --it) { + KexiDB::QueryColumnInfo *ci = ((*it) < fieldsExpandedSize) ? fieldsExpanded.at( *it ) : 0; + if (!ci) { + kdWarning() << "KexiComboBoxPopup::setData(): " << *it << " >= fieldsExpandedSize" << endl; + continue; + } + KexiDB::VariableExpr *fieldExpr + = new KexiDB::VariableExpr( ci->field->table()->name()+"."+ci->field->name() ); + fieldExpr->field = ci->field; + fieldExpr->tablePositionForField = d->privateQuery->tableBoundToColumn( *it ); + if (expr) { +//! @todo " " separator hardcoded... +//! @todo use SQL sub-parser here... + KexiDB::ConstExpr *constExpr = new KexiDB::ConstExpr(CHARACTER_STRING_LITERAL, " "); + expr = new KexiDB::BinaryExpr(KexiDBExpr_Arithm, constExpr, CONCATENATION, expr); + expr = new KexiDB::BinaryExpr(KexiDBExpr_Arithm, fieldExpr, CONCATENATION, expr); + } + else + expr = fieldExpr; + } + expr->debug(); + kdDebug() << expr->toString() << endl; + + KexiDB::Field *f = new KexiDB::Field(); + f->setExpression( expr ); + d->privateQuery->addField( f ); +#if 0 //does not work yet +// <remove later> +//! @todo temp: improved display by hiding all columns except the computed one + const int numColumntoHide = d->privateQuery->fieldsExpanded().count() - 1; + for (int i=0; i < numColumntoHide; i++) + d->privateQuery->setColumnVisible(i, false); +// </remove later> +#endif +//todo... + kdDebug() << "--- Private query: " << endl; + d->privateQuery->debug(); + cursor = field->table()->connection()->prepareQuery( *d->privateQuery ); + } + if (!cursor) +//! @todo errmsg + return; + + if (d->tv->data()) + d->tv->data()->disconnect( this ); + d->tv->setData( cursor ); + + connect( d->tv, SIGNAL(dataRefreshed()), this, SLOT(slotDataReloadRequested())); + updateSize(); + return; + } + + kdWarning() << "KexiComboBoxPopup::setData(KexiTableViewColumn &): no column relatedData \n - moving to setData(KexiDB::Field &)" << endl; + + // case 3: enum hints + d->tv->setColumnStretchEnabled( true, -1 ); //only needed when using single column + +//! @todo THIS IS PRIMITIVE: we'd need to employ KexiDB::Reference here! + d->int_f = new KexiDB::Field(field->name(), KexiDB::Field::Text); + KexiTableViewData *data = new KexiTableViewData(); + data->addColumn( new KexiTableViewColumn( *d->int_f ) ); + QValueVector<QString> hints = field->enumHints(); + for(uint i=0; i < hints.size(); i++) { + KexiTableItem *item = data->createItem();//new KexiTableItem(1); + (*item)[0]=QVariant(hints[i]); + kdDebug() << "added: '" << hints[i] <<"'"<<endl; + data->append( item ); + } + setDataInternal( data, true ); +} + +void KexiComboBoxPopup::setDataInternal( KexiTableViewData *data, bool owner ) +{ + if (d->tv->data()) + d->tv->data()->disconnect( this ); + d->tv->setData( data, owner ); + connect( d->tv, SIGNAL(dataRefreshed()), this, SLOT(slotDataReloadRequested())); + + updateSize(); +} + +void KexiComboBoxPopup::updateSize(int minWidth) +{ + const int rows = QMIN( d->max_rows, d->tv->rows() ); + + d->tv->adjustColumnWidthToContents(-1); + + KexiTableEdit *te = dynamic_cast<KexiTableEdit*>(parentWidget()); + const int width = QMAX( d->tv->tableSize().width(), + (te ? te->totalSize().width() : (parentWidget()?parentWidget()->width():0/*sanity*/)) ); + kexidbg << "KexiComboBoxPopup::updateSize(): size=" << size() << endl; + resize( QMAX(minWidth, width)/*+(d->tv->columns()>1?2:0)*/ /*(d->updateSizeCalled?0:1)*/, d->tv->rowHeight() * rows +2 ); + kexidbg << "KexiComboBoxPopup::updateSize(): size after=" << size() << endl; + + //stretch the last column + d->tv->setColumnStretchEnabled(true, d->tv->columns()-1); +} + +KexiTableView* KexiComboBoxPopup::tableView() +{ + return d->tv; +} + +void KexiComboBoxPopup::resize( int w, int h ) +{ + d->tv->horizontalScrollBar()->hide(); + d->tv->verticalScrollBar()->hide(); + d->tv->move(1,1); + d->tv->resize( w-2, h-2 ); + QFrame::resize(w,h); + update(); + updateGeometry(); +} + +void KexiComboBoxPopup::setMaxRows(int r) +{ + d->max_rows = r; +} + +int KexiComboBoxPopup::maxRows() const +{ + return d->max_rows; +} + +void KexiComboBoxPopup::slotTVItemAccepted(KexiTableItem *item, int row, int) +{ + hide(); + emit rowAccepted(item, row); +} + +bool KexiComboBoxPopup::eventFilter( QObject *o, QEvent *e ) +{ + if (o==this && e->type()==QEvent::Hide) { + emit hidden(); + } + else if (e->type()==QEvent::MouseButtonPress) { + kdDebug() << "QEvent::MousePress" << endl; + } + else if (o==d->tv) { + if (e->type()==QEvent::KeyPress) { + QKeyEvent *ke = static_cast<QKeyEvent*>(e); + const int k = ke->key(); + if ((ke->state()==NoButton && (k==Key_Escape || k==Key_F4)) + || (ke->state()==AltButton && k==Key_Up)) + { + hide(); + emit cancelled(); + return true; + } + } + } + return QFrame::eventFilter( o, e ); +} + +void KexiComboBoxPopup::slotDataReloadRequested() +{ + updateSize(); +} + +#include "kexicomboboxpopup.moc" diff --git a/kexi/widget/tableview/kexicomboboxpopup.h b/kexi/widget/tableview/kexicomboboxpopup.h new file mode 100644 index 00000000..42a15404 --- /dev/null +++ b/kexi/widget/tableview/kexicomboboxpopup.h @@ -0,0 +1,92 @@ +/* This file is part of the KDE project + Copyright (C) 2004-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. + */ + +#ifndef KEXICOMBOBOXPOPUP_H +#define KEXICOMBOBOXPOPUP_H + +#include <qframe.h> + +class KexiComboBoxPopupPrivate; +class KexiTableView; +class KexiTableViewData; +class KexiTableViewColumn; +class KexiTableItem; +namespace KexiDB { + class Field; +} + +//! Internal class for displaying popup table view +class KexiComboBoxPopup : public QFrame +{ + Q_OBJECT + public: +//js TODO: more ctors! + /*! Constructor for creating a popup using definition from \a column. + If the column is lookup column, it's definition is used to display + one or more column within the popup. Otherwise column.field() is used + to display single-column data. */ + KexiComboBoxPopup(QWidget* parent, KexiTableViewColumn &column); + + /*! Alternative constructor supporting lookup fields and enum hints. */ + KexiComboBoxPopup(QWidget* parent, KexiDB::Field &field); + + virtual ~KexiComboBoxPopup(); + + KexiTableView* tableView(); + + /*! Sets maximum number of rows for this popup. */ + void setMaxRows(int r); + + /*! \return maximum number of rows for this popup. */ + int maxRows() const; + + /*! Default maximum number of rows for KexiComboBoxPopup objects. */ + static const int defaultMaxRows; + + virtual bool eventFilter( QObject *o, QEvent *e ); + + signals: + void rowAccepted(KexiTableItem *item, int row); + void cancelled(); + void hidden(); + + public slots: + virtual void resize( int w, int h ); + void updateSize(int minWidth = 0); + + protected slots: + void slotTVItemAccepted(KexiTableItem *item, int row, int col); + void slotDataReloadRequested(); + + protected: + void init(); + //! The main function for setting data; data can be set either by passing \a column or \a field. + //! The second case is used for lookup + void setData(KexiTableViewColumn *column, KexiDB::Field *field); + + //! used by setData() + void setDataInternal( KexiTableViewData *data, bool owner = true ); //!< helper + + KexiComboBoxPopupPrivate *d; + + friend class KexiComboBoxTableEdit; +}; + +#endif + diff --git a/kexi/widget/tableview/kexicomboboxtableedit.cpp b/kexi/widget/tableview/kexicomboboxtableedit.cpp new file mode 100644 index 00000000..75815a8a --- /dev/null +++ b/kexi/widget/tableview/kexicomboboxtableedit.cpp @@ -0,0 +1,446 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Peter Simonsson <psn@linux.se> + Copyright (C) 2003-2006 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 <qlayout.h> +#include <qstyle.h> +#include <qwindowsstyle.h> +#include <qpainter.h> +#include <qapplication.h> +#include <qclipboard.h> + +#include "kexicomboboxtableedit.h" +#include <widget/utils/kexicomboboxdropdownbutton.h> +#include "kexicomboboxpopup.h" +#include "kexitableview.h" +#include "kexitableitem.h" +#include "kexi.h" + +#include <klineedit.h> + +//! @internal +class KexiComboBoxTableEdit::Private +{ +public: + Private() + : popup(0) + , currentEditorWidth(0) + , visibleTableViewColumn(0) + , internalEditor(0) + { + } + ~Private() + { + delete internalEditor; + delete visibleTableViewColumn; + } + + KPushButton *button; + KexiComboBoxPopup *popup; + int currentEditorWidth; + QSize totalSize; + KexiTableViewColumn* visibleTableViewColumn; + KexiTableEdit* internalEditor; +}; + +//====================================================== + +KexiComboBoxTableEdit::KexiComboBoxTableEdit(KexiTableViewColumn &column, QWidget *parent) + : KexiInputTableEdit(column, parent) + , KexiComboBoxBase() + , d(new Private()) +{ + setName("KexiComboBoxTableEdit"); + m_setVisibleValueOnSetValueInternal = true; + d->button = new KexiComboBoxDropDownButton( parentWidget() /*usually a viewport*/ ); + d->button->hide(); + d->button->setFocusPolicy( NoFocus ); + connect(d->button, SIGNAL(clicked()), this, SLOT(slotButtonClicked())); + + connect(m_lineedit, SIGNAL(textChanged(const QString&)), this, SLOT(slotLineEditTextChanged(const QString&))); + +// m_lineedit = new KLineEdit(this, "lineedit"); +// m_lineedit->setFrame(false); +// m_lineedit->setFrameStyle( QFrame::Plain | QFrame::Box ); +// m_lineedit->setLineWidth( 1 ); +// if (f.isNumericType()) { +// m_lineedit->setAlignment(AlignRight); +// } +// setView( m_lineedit ); + +// layout->addWidget(m_view); +// m_combo->setEditable( true ); +// m_combo->clear(); +// m_combo->insertStringList(f.enumHints()); +// QStringList::ConstIterator it, end( f.enumHints().constEnd() ); +// for ( it = f.enumHints().constBegin(); it != end; ++it) { +// if(!hints.at(i).isEmpty()) +// m_combo->insertItem(hints.at(i)); +// } + +//js: TODO +//js static_cast<KComboBox*>(m_view)->insertStringList(list); +//js static_cast<KComboBox*>(m_view)->setCurrentItem(static_cast<int>(t)); +} + +KexiComboBoxTableEdit::~KexiComboBoxTableEdit() +{ + delete d; +} + +void KexiComboBoxTableEdit::createInternalEditor(KexiDB::QuerySchema& schema) +{ + if (!m_column->visibleLookupColumnInfo || d->visibleTableViewColumn/*sanity*/) + return; + const KexiDB::Field::Type t = m_column->visibleLookupColumnInfo->field->type(); +//! @todo subtype? + KexiCellEditorFactoryItem *item = KexiCellEditorFactory::item(t); + if (!item || item->className()=="KexiInputTableEdit") + return; //unsupported type or there is no need to use subeditor for KexiInputTableEdit + //special cases: BLOB, Bool datatypes +//todo + //find real type to display + KexiDB::QueryColumnInfo *ci = m_column->visibleLookupColumnInfo; + KexiDB::QueryColumnInfo *visibleLookupColumnInfo = 0; + if (ci->indexForVisibleLookupValue() != -1) { + //Lookup field is defined + visibleLookupColumnInfo = schema.expandedOrInternalField( ci->indexForVisibleLookupValue() ); + } + d->visibleTableViewColumn = new KexiTableViewColumn(schema, *ci, visibleLookupColumnInfo); +//! todo set d->internalEditor visible and use it to enable data entering by hand + d->internalEditor = KexiCellEditorFactory::createEditor(*d->visibleTableViewColumn, 0); + m_lineedit->hide(); +} + +KexiComboBoxPopup *KexiComboBoxTableEdit::popup() const +{ + return d->popup; +} + +void KexiComboBoxTableEdit::setPopup(KexiComboBoxPopup *popup) +{ + d->popup = popup; +} + +void KexiComboBoxTableEdit::showFocus( const QRect& r, bool readOnly ) +{ +// d->button->move( pos().x()+ width(), pos().y() ); + updateFocus( r ); + d->button->setEnabled(!readOnly); + if (readOnly) + d->button->hide(); + else + d->button->show(); +} + +void KexiComboBoxTableEdit::resize(int w, int h) +{ + d->totalSize = QSize(w,h); + if (!column()->isReadOnly()) { + d->button->resize( h, h ); + QWidget::resize(w - d->button->width(), h); + } + m_rightMarginWhenFocused = m_rightMargin + (column()->isReadOnly() ? 0 : d->button->width()); + QRect r( pos().x(), pos().y(), w+1, h+1 ); + if (m_scrollView) + r.moveBy(m_scrollView->contentsX(), m_scrollView->contentsY()); + updateFocus( r ); + if (popup()) { + popup()->updateSize(); + } +} + +// internal +void KexiComboBoxTableEdit::updateFocus( const QRect& r ) +{ + if (!column()->isReadOnly()) { + if (d->button->width() > r.width()) + moveChild(d->button, r.right() + 1, r.top()); + else + moveChild(d->button, r.right() - d->button->width(), r.top() ); + } +} + +void KexiComboBoxTableEdit::hideFocus() +{ + d->button->hide(); +} + +QVariant KexiComboBoxTableEdit::visibleValue() +{ + return KexiComboBoxBase::visibleValue(); +/* KexiDB::LookupFieldSchema *lookupFieldSchema = this->lookupFieldSchema(); + if (!popup() || !lookupFieldSchema) + return QVariant(); + KexiTableItem *it = popup()->tableView()->selectedItem(); + return it ? it->at( lookupFieldSchema->visibleColumn() ) : QVariant();*/ +} + +void KexiComboBoxTableEdit::clear() +{ + m_lineedit->clear(); + KexiComboBoxBase::clear(); +} + +bool KexiComboBoxTableEdit::valueChanged() +{ + const tristate res = valueChangedInternal(); + if (~res) //no result: just compare values + return KexiInputTableEdit::valueChanged(); + return res == true; +} + +void KexiComboBoxTableEdit::paintFocusBorders( QPainter *p, QVariant &, int x, int y, int w, int h ) +{ +// d->currentEditorWidth = w; + if (!column()->isReadOnly()) { + if (w > d->button->width()) + w -= d->button->width(); + } + p->drawRect(x, y, w, h); +} + +void KexiComboBoxTableEdit::setupContents( QPainter *p, bool focused, const QVariant& val, + QString &txt, int &align, int &x, int &y_offset, int &w, int &h ) +{ + if (d->internalEditor) { + d->internalEditor->setupContents( p, focused, val, txt, align, x, y_offset, w, h ); + } + else { + KexiInputTableEdit::setupContents( p, focused, val, txt, align, x, y_offset, w, h ); + } + if (!column()->isReadOnly() && focused && (w > d->button->width())) + w -= (d->button->width() - x); + if (!val.isNull()) { + KexiTableViewData *relData = column()->relatedData(); + KexiDB::LookupFieldSchema *lookupFieldSchema = 0; + if (relData) { + int rowToHighlight; + txt = valueForString(val.toString(), &rowToHighlight, 0, 1); + } + else if ((lookupFieldSchema = this->lookupFieldSchema())) { + /* handled at at KexiTableView level + if (popup()) { + KexiTableItem *it = popup()->tableView()->selectedItem(); + if (it && lookupFieldSchema->visibleColumn()!=-1 && (int)it->size() >= lookupFieldSchema->visibleColumn()) + txt = it->at( lookupFieldSchema->visibleColumn() ).toString(); + }*/ + } + else { + //use 'enum hints' model + txt = field()->enumHint( val.toInt() ); + } + } +} + +void KexiComboBoxTableEdit::slotButtonClicked() +{ + // this method is sometimes called by hand: + // do not allow to simulate clicks when the button is disabled + if (column()->isReadOnly() || !d->button->isEnabled()) + return; + + if (m_mouseBtnPressedWhenPopupVisible) { + m_mouseBtnPressedWhenPopupVisible = false; + d->button->setOn(false); + return; + } + kdDebug() << "KexiComboBoxTableEdit::slotButtonClicked()" << endl; + if (!popup() || !popup()->isVisible()) { + kdDebug() << "SHOW POPUP" << endl; + showPopup(); + d->button->setOn(true); + } +} + +void KexiComboBoxTableEdit::slotPopupHidden() +{ + d->button->setOn(false); +// d->currentEditorWidth = 0; +} + +void KexiComboBoxTableEdit::updateButton() +{ + d->button->setOn(popup()->isVisible()); +} + +void KexiComboBoxTableEdit::hide() +{ + KexiInputTableEdit::hide(); + KexiComboBoxBase::hide(); + d->button->setOn(false); +} + +void KexiComboBoxTableEdit::show() +{ + KexiInputTableEdit::show(); + if (!column()->isReadOnly()) { + d->button->show(); + } +} + +bool KexiComboBoxTableEdit::handleKeyPress( QKeyEvent *ke, bool editorActive ) +{ + const int k = ke->key(); + if ((ke->state()==NoButton && k==Qt::Key_F4) + || (ke->state()==AltButton && k==Qt::Key_Down)) + { + //show popup + slotButtonClicked(); + return true; + } + else if (editorActive) { + const bool enterPressed = k==Qt::Key_Enter || k==Qt::Key_Return; + if (enterPressed && m_internalEditorValueChanged) { + createPopup(false); + selectItemForEnteredValueInLookupTable( m_userEnteredValue ); + return false; + } + + return handleKeyPressForPopup( ke ); + } + + return false; +} + +void KexiComboBoxTableEdit::slotLineEditTextChanged(const QString& s) +{ + slotInternalEditorValueChanged(s); +} + +int KexiComboBoxTableEdit::widthForValue( QVariant &val, const QFontMetrics &fm ) +{ + KexiTableViewData *relData = column() ? column()->relatedData() : 0; + if (lookupFieldSchema() || relData) { + // in 'lookupFieldSchema' or or 'related table data' model + // we're assuming val is already the text, not the index +//! @todo ok? + return QMAX(KEXITV_MINIMUM_COLUMN_WIDTH, fm.width(val.toString())); + } + //use 'enum hints' model + QValueVector<QString> hints = field()->enumHints(); + bool ok; + int idx = val.toInt(&ok); + if (!ok || idx < 0 || idx > int(hints.size()-1)) + return KEXITV_MINIMUM_COLUMN_WIDTH; + QString txt = hints.at( idx, &ok ); + if (!ok) + return KEXITV_MINIMUM_COLUMN_WIDTH; + return fm.width( txt ); +} + +bool KexiComboBoxTableEdit::eventFilter( QObject *o, QEvent *e ) +{ + if (!column()->isReadOnly() && e->type()==QEvent::MouseButtonPress && m_scrollView) { + QPoint gp = static_cast<QMouseEvent*>(e)->globalPos() + + QPoint(m_scrollView->childX(d->button), m_scrollView->childY(d->button)); + QRect r(d->button->mapToGlobal(d->button->geometry().topLeft()), + d->button->mapToGlobal(d->button->geometry().bottomRight())); + if (o==popup() && popup()->isVisible() && r.contains( gp )) { + m_mouseBtnPressedWhenPopupVisible = true; + } + } + return false; +} + +QSize KexiComboBoxTableEdit::totalSize() const +{ + return d->totalSize; +} + +QWidget *KexiComboBoxTableEdit::internalEditor() const +{ + return m_lineedit; +} + +void KexiComboBoxTableEdit::moveCursorToEndInInternalEditor() +{ + moveCursorToEnd(); +} + +void KexiComboBoxTableEdit::selectAllInInternalEditor() +{ + selectAll(); +} + +void KexiComboBoxTableEdit::moveCursorToEnd() +{ + m_lineedit->end(false/*!mark*/); +} + +void KexiComboBoxTableEdit::moveCursorToStart() +{ + m_lineedit->home(false/*!mark*/); +} + +void KexiComboBoxTableEdit::selectAll() +{ + m_lineedit->selectAll(); +} + +void KexiComboBoxTableEdit::setValueInInternalEditor(const QVariant& value) +{ + m_lineedit->setText(value.toString()); +} + +QVariant KexiComboBoxTableEdit::valueFromInternalEditor() +{ + return m_lineedit->text(); +} + +QPoint KexiComboBoxTableEdit::mapFromParentToGlobal(const QPoint& pos) const +{ + KexiTableView *tv = dynamic_cast<KexiTableView*>(m_scrollView); + if (!tv) + return QPoint(-1,-1); + return tv->viewport()->mapToGlobal(pos); +} + +int KexiComboBoxTableEdit::popupWidthHint() const +{ + return m_lineedit->width() + m_leftMargin + m_rightMarginWhenFocused; //QMAX(popup()->width(), d->currentEditorWidth); +} + +void KexiComboBoxTableEdit::handleCopyAction(const QVariant& value, const QVariant& visibleValue) +{ + Q_UNUSED(value); +//! @todo does not work with BLOBs! + qApp->clipboard()->setText( visibleValue.toString() ); +} + +void KexiComboBoxTableEdit::handleAction(const QString& actionName) +{ + const bool alreadyVisible = m_lineedit->isVisible(); + + if (actionName=="edit_paste") { + if (!alreadyVisible) { //paste as the entire text if the cell was not in edit mode + emit editRequested(); + m_lineedit->clear(); + } +//! @todo does not work with BLOBs! + setValueInInternalEditor( qApp->clipboard()->text() ); + } + else + KexiInputTableEdit::handleAction(actionName); +} + + +KEXI_CELLEDITOR_FACTORY_ITEM_IMPL(KexiComboBoxEditorFactoryItem, KexiComboBoxTableEdit) + +#include "kexicomboboxtableedit.moc" diff --git a/kexi/widget/tableview/kexicomboboxtableedit.h b/kexi/widget/tableview/kexicomboboxtableedit.h new file mode 100644 index 00000000..713fa55e --- /dev/null +++ b/kexi/widget/tableview/kexicomboboxtableedit.h @@ -0,0 +1,166 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Peter Simonsson <psn@linux.se> + Copyright (C) 2003-2006 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. + */ + +#ifndef _KEXICOMBOBOXTABLEEDIT_H_ +#define _KEXICOMBOBOXTABLEEDIT_H_ + +#include "kexidb/field.h" +#include "kexiinputtableedit.h" +#include "kexicomboboxbase.h" +#include <kexidb/lookupfieldschema.h> + +class KPushButton; +class KLineEdit; +class KexiComboBoxPopup; +class KexiTableItem; +class KexiTableViewColumn; + +/*! @short Drop-down cell editor. +*/ +class KexiComboBoxTableEdit : public KexiInputTableEdit, public KexiComboBoxBase +{ + Q_OBJECT + + public: + KexiComboBoxTableEdit(KexiTableViewColumn &column, QWidget *parent=0); + virtual ~KexiComboBoxTableEdit(); + + //! Implemented for KexiComboBoxBase + virtual KexiTableViewColumn *column() const { return m_column; } + + //! Implemented for KexiComboBoxBase + virtual KexiDB::Field *field() const { return m_column->field(); } + + //! Implemented for KexiComboBoxBase + virtual QVariant origValue() const { return m_origValue; } + + virtual void setValueInternal(const QVariant& add, bool removeOld) + { KexiComboBoxBase::setValueInternal(add, removeOld); } + + virtual QVariant value() { return KexiComboBoxBase::value(); } + + virtual void clear(); + + virtual bool valueChanged(); + + virtual QVariant visibleValue(); + + /*! Reimplemented: resizes a view(). */ + virtual void resize(int w, int h); + + virtual void showFocus( const QRect& r, bool readOnly ); + + virtual void hideFocus(); + + virtual void paintFocusBorders( QPainter *p, QVariant &cal, int x, int y, int w, int h ); + + /*! Setups contents of the cell. As a special case, if there is lookup field schema + defined, \a val already contains the visible value (usually the text) + set by \ref KexiTableView::paintcell(), so there is noo need to lookup the value + in the combo box's popup. */ + virtual void setupContents( QPainter *p, bool focused, const QVariant& val, + QString &txt, int &align, int &x, int &y_offset, int &w, int &h ); + + /*! Used to handle key press events for the item. */ + virtual bool handleKeyPress( QKeyEvent *ke, bool editorActive ); + + virtual int widthForValue( QVariant &val, const QFontMetrics &fm ); + + virtual void hide(); + virtual void show(); + + /*! \return total size of this editor, including popup button. */ + virtual QSize totalSize() const; + + virtual void createInternalEditor(KexiDB::QuerySchema& schema); + + /*! Reimplemented after KexiInputTableEdit. */ + virtual void handleAction(const QString& actionName); + + /*! Reimplemented after KexiInputTableEdit. + For a special case (combo box), \a visibleValue can be provided, + so it can be copied to the clipboard instead of unreadable \a value. */ + virtual void handleCopyAction(const QVariant& value, const QVariant& visibleValue); + + public slots: + //! Implemented for KexiDataItemInterface + virtual void moveCursorToEnd(); + + //! Implemented for KexiDataItemInterface + virtual void moveCursorToStart(); + + //! Implemented for KexiDataItemInterface + virtual void selectAll(); + + protected slots: + void slotButtonClicked(); + void slotRowAccepted(KexiTableItem *item, int row) { KexiComboBoxBase::slotRowAccepted(item, row); } + void slotItemSelected(KexiTableItem* item) { KexiComboBoxBase::slotItemSelected(item); } + void slotInternalEditorValueChanged(const QVariant& v) + { KexiComboBoxBase::slotInternalEditorValueChanged(v); } + void slotLineEditTextChanged(const QString& s); + void slotPopupHidden(); + + protected: + //! internal + void updateFocus( const QRect& r ); + + virtual bool eventFilter( QObject *o, QEvent *e ); + + //! Implemented for KexiComboBoxBase + virtual QWidget *internalEditor() const; + + //! Implemented for KexiComboBoxBase + virtual void moveCursorToEndInInternalEditor(); + + //! Implemented for KexiComboBoxBase + virtual void selectAllInInternalEditor(); + + //! Implemented for KexiComboBoxBase + virtual void setValueInInternalEditor(const QVariant& value); + + //! Implemented for KexiComboBoxBase + virtual QVariant valueFromInternalEditor(); + + //! Implemented for KexiComboBoxBase + virtual void editRequested() { KexiInputTableEdit::editRequested(); } + + //! Implemented for KexiComboBoxBase + virtual void acceptRequested() { KexiInputTableEdit::acceptRequested(); } + + //! Implemented for KexiComboBoxBase + virtual QPoint mapFromParentToGlobal(const QPoint& pos) const; + + //! Implemented for KexiComboBoxBase + virtual int popupWidthHint() const; + + //! Implemented this to update button state. + virtual void updateButton(); + + virtual KexiComboBoxPopup *popup() const; + virtual void setPopup(KexiComboBoxPopup *popup); + + class Private; + Private *d; +}; + +KEXI_DECLARE_CELLEDITOR_FACTORY_ITEM(KexiComboBoxEditorFactoryItem) + +#endif diff --git a/kexi/widget/tableview/kexidataawareobjectiface.cpp b/kexi/widget/tableview/kexidataawareobjectiface.cpp new file mode 100644 index 00000000..59edbed3 --- /dev/null +++ b/kexi/widget/tableview/kexidataawareobjectiface.cpp @@ -0,0 +1,2108 @@ +/* This file is part of the KDE project + Copyright (C) 2005-2007 Jaroslaw Staniek <js@iidea.pl> + + Based on KexiTableView code. + Copyright (C) 2002 Till Busch <till@bux.at> + Copyright (C) 2003 Lucijan Busch <lucijan@gmx.at> + Copyright (C) 2003 Daniel Molkentin <molkentin@kde.org> + Copyright (C) 2003 Joseph Wenninger <jowenn@kde.org> + + 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 "kexidataawareobjectiface.h" + +#include <qscrollview.h> +#include <qlabel.h> +#include <qtooltip.h> + +#include <kmessagebox.h> + +#include <kexi.h> +#include <kexiutils/validator.h> +#include <widget/utils/kexirecordnavigator.h> +#include <widget/utils/kexirecordmarker.h> +#include <kexidb/roweditbuffer.h> +#include <kexidataiteminterface.h> + +#include "kexitableviewheader.h" + +using namespace KexiUtils; + +KexiDataAwareObjectInterface::KexiDataAwareObjectInterface() +{ + m_data = 0; + m_itemIterator = 0; + m_readOnly = -1; //don't know + m_insertingEnabled = -1; //don't know + m_isSortingEnabled = true; + m_isFilteringEnabled = true; + m_deletionPolicy = AskDelete; + m_inside_acceptEditor = false; + m_acceptsRowEditAfterCellAccepting = false; + m_internal_acceptsRowEditAfterCellAccepting = false; + m_contentsMousePressEvent_dblClick = false; + m_navPanel = 0; + m_initDataContentsOnShow = false; + m_cursorPositionSetExplicityBeforeShow = false; + m_verticalHeader = 0; + m_horizontalHeader = 0; + m_insertItem = 0; +// m_rowEditBuffer = 0; + m_spreadSheetMode = false; + m_dropsAtRowEnabled = false; + m_updateEntireRowWhenMovingToOtherRow = false; + m_dragIndicatorLine = -1; + m_emptyRowInsertingEnabled = false; + m_popupMenu = 0; + m_contextMenuEnabled = true; + m_rowWillBeDeleted = -1; + m_alsoUpdateNextRow = false; + m_verticalHeaderAlreadyAdded = false; + m_vScrollBarValueChanged_enabled = true; + m_scrollbarToolTipsEnabled = true; + m_scrollBarTipTimerCnt = 0; + m_scrollBarTip = 0; + m_recentSearchDirection = KexiSearchAndReplaceViewInterface::Options::DefaultSearchDirection; + + // setup scrollbar tooltip and related members + m_scrollBarTip = new QLabel("",0, "vScrollBarToolTip", + Qt::WStyle_Customize |Qt::WStyle_NoBorder|Qt::WX11BypassWM|Qt::WStyle_StaysOnTop|Qt::WStyle_Tool); + m_scrollBarTip->setPalette(QToolTip::palette()); + m_scrollBarTip->setMargin(2); + m_scrollBarTip->setIndent(0); + m_scrollBarTip->setAlignment(Qt::AlignCenter); + m_scrollBarTip->setFrameStyle( QFrame::Plain | QFrame::Box ); + m_scrollBarTip->setLineWidth(1); + + clearVariables(); +} + +KexiDataAwareObjectInterface::~KexiDataAwareObjectInterface() +{ + delete m_insertItem; +// delete m_rowEditBuffer; + delete m_itemIterator; + delete m_scrollBarTip; + //we cannot delete m_data here... subclasses should do this +} + +void KexiDataAwareObjectInterface::clearVariables() +{ + m_editor = 0; +// m_rowEditBuffer = 0; + m_rowEditing = false; + m_newRowEditing = false; + m_curRow = -1; + m_curCol = -1; + m_currentItem = 0; +} + +void KexiDataAwareObjectInterface::setData( KexiTableViewData *data, bool owner ) +{ + const bool theSameData = m_data && m_data==data; + if (m_owner && m_data && m_data!=data/*don't destroy if it's the same*/) { + kexidbg << "KexiDataAwareObjectInterface::setData(): destroying old data (owned)" << endl; + delete m_itemIterator; + delete m_data; //destroy old data + m_data = 0; + m_itemIterator = 0; + } + m_owner = owner; + m_data = data; + if (m_data) + m_itemIterator = m_data->createIterator(); + + kdDebug(44021) << "KexiDataAwareObjectInterface::setData(): using shared data" << endl; + //add columns +//OK? + clearColumnsInternal(false); + if (m_data) { + int i = 0; + for (KexiTableViewColumn::ListIterator it(m_data->columns); + it.current(); ++it, i++) + { + KexiDB::Field *f = it.current()->field(); + if (it.current()->visible()) { + int wid = f->width(); + if (wid==0) + wid=KEXI_DEFAULT_DATA_COLUMN_WIDTH;//default col width in pixels +//! @todo add col width configuration and storage + addHeaderColumn(it.current()->isHeaderTextVisible() + ? it.current()->captionAliasOrName() : QString::null, + f->description(), it.current()->icon(), wid); + } + } + } + if (m_verticalHeader) { + m_verticalHeader->clear(); + if (m_data) + m_verticalHeader->addLabels(m_data->count()); + } + if (m_data && m_data->count()==0) + m_navPanel->setCurrentRecordNumber(0+1); + + if (m_data && !theSameData) { +//! @todo: store sorting settings? + setSorting(-1); +// connect(m_data, SIGNAL(refreshRequested()), this, SLOT(slotRefreshRequested())); + connectToReloadDataSlot(m_data, SIGNAL(reloadRequested())); + QObject* thisObject = dynamic_cast<QObject*>(this); + if (thisObject) { + QObject::connect(m_data, SIGNAL(destroying()), thisObject, SLOT(slotDataDestroying())); + QObject::connect(m_data, SIGNAL(rowsDeleted( const QValueList<int> & )), + thisObject, SLOT(slotRowsDeleted( const QValueList<int> & ))); + QObject::connect(m_data, SIGNAL(aboutToDeleteRow(KexiTableItem&,KexiDB::ResultInfo*,bool)), + thisObject, SLOT(slotAboutToDeleteRow(KexiTableItem&,KexiDB::ResultInfo*,bool))); + QObject::connect(m_data, SIGNAL(rowDeleted()), thisObject, SLOT(slotRowDeleted())); + QObject::connect(m_data, SIGNAL(rowInserted(KexiTableItem*,bool)), + thisObject, SLOT(slotRowInserted(KexiTableItem*,bool))); + QObject::connect(m_data, SIGNAL(rowInserted(KexiTableItem*,uint,bool)), + thisObject, SLOT(slotRowInserted(KexiTableItem*,uint,bool))); //not db-aware + QObject::connect(m_data, SIGNAL(rowRepaintRequested(KexiTableItem&)), + thisObject, SLOT(slotRowRepaintRequested(KexiTableItem&))); + // setup scrollbar's tooltip + QObject::connect(verticalScrollBar(),SIGNAL(sliderReleased()), + thisObject,SLOT(vScrollBarSliderReleased())); + QObject::connect(verticalScrollBar(),SIGNAL(valueChanged(int)), + thisObject,SLOT(vScrollBarValueChanged(int))); + QObject::connect(&m_scrollBarTipTimer,SIGNAL(timeout()), + thisObject,SLOT(scrollBarTipTimeout())); + } + } + + if (!m_data) { +// clearData(); + cancelRowEdit(); + //m_data->clearInternal(); + clearVariables(); + } + else { + if (!m_insertItem) {//first setData() call - add 'insert' item + m_insertItem = m_data->createItem(); //new KexiTableItem(m_data->columns.count()); + } + else {//just reinit + m_insertItem->init(m_data->columns.count()); + } + } + + //update gui mode + m_navPanel->setInsertingEnabled(m_data && isInsertingEnabled()); + if (m_verticalHeader) + m_verticalHeader->showInsertRow(m_data && isInsertingEnabled()); + + initDataContents(); + updateIndicesForVisibleValues(); + + if (m_data) + /*emit*/ dataSet( m_data ); +} + +void KexiDataAwareObjectInterface::initDataContents() +{ + m_editor = 0; +// QSize s(tableSize()); +// resizeContents(s.width(),s.height()); + + m_navPanel->setRecordCount(rows()); + + if (m_data && !m_cursorPositionSetExplicityBeforeShow) { + //set current row: + m_currentItem = 0; + int curRow = -1, curCol = -1; + if (m_data->columnsCount()>0) { + if (rows()>0) { + m_itemIterator->toFirst(); + m_currentItem = **m_itemIterator; + curRow = 0; + curCol = 0; + } + else {//no data + if (isInsertingEnabled()) { + m_currentItem = m_insertItem; + curRow = 0; + curCol = 0; + } + } + } + setCursorPosition(curRow, curCol, true/*force*/); + } + ensureCellVisible(m_curRow, m_curCol); +// updateRowCountInfo(); +// setNavRowCount(rows()); + +//OK? +// updateContents(); + updateWidgetContents(); + + m_cursorPositionSetExplicityBeforeShow = false; + + /*emit*/ dataRefreshed(); +} + +void KexiDataAwareObjectInterface::setSortingEnabled(bool set) +{ + if (m_isSortingEnabled && !set) + setSorting(-1); + m_isSortingEnabled = set; + /*emit*/ reloadActions(); +} + +void KexiDataAwareObjectInterface::setSorting(int col, bool ascending) +{ + if (!m_data || !m_isSortingEnabled) + return; +// d->pTopHeader->setSortIndicator(col, ascending ? Ascending : Descending); + setLocalSortingOrder(col, ascending ? 1 : -1); + m_data->setSorting(col, ascending); +} + +int KexiDataAwareObjectInterface::dataSortedColumn() const +{ + if (m_data && m_isSortingEnabled) + return m_data->sortedColumn(); + return -1; +} + +int KexiDataAwareObjectInterface::dataSortingOrder() const +{ + return m_data ? m_data->sortingOrder() : 0; +} + +bool KexiDataAwareObjectInterface::sort() +{ + if (!m_data || !m_isSortingEnabled) + return false; + + if (rows() < 2) + return true; + + if (!acceptRowEdit()) + return false; + + const int oldRow = m_curRow; + if (m_data->sortedColumn()!=-1) + m_data->sort(); + + //locate current record + if (!m_currentItem) { + m_itemIterator->toFirst(); + m_currentItem = **m_itemIterator; //m_data->first(); + m_curRow = 0; + if (!m_currentItem) + return true; + } + if (m_currentItem != m_insertItem) { + m_curRow = m_data->findRef(m_currentItem); + int jump = m_curRow - oldRow; + if (jump<0) + (*m_itemIterator) -= -jump; + else + (*m_itemIterator) += jump; + } + + updateGUIAfterSorting(); + editorShowFocus( m_curRow, m_curCol ); + if (m_verticalHeader) + m_verticalHeader->setCurrentRow(m_curRow); + if (m_horizontalHeader) + m_horizontalHeader->setSelectedSection(m_curCol); + if (m_navPanel) + m_navPanel->setCurrentRecordNumber(m_curRow+1); + return true; +} + +void KexiDataAwareObjectInterface::sortAscending() +{ + if (currentColumn()<0) + return; + sortColumnInternal( currentColumn(), 1 ); +} + +void KexiDataAwareObjectInterface::sortDescending() +{ + if (currentColumn()<0) + return; + sortColumnInternal( currentColumn(), -1 ); +} + +void KexiDataAwareObjectInterface::sortColumnInternal(int col, int order) +{ + //-select sorting + bool asc; + if (order == 0) {// invert + if (col==dataSortedColumn() && dataSortingOrder()==1) + asc = dataSortingOrder()==-1; //inverse sorting for this column -> descending order + else + asc = true; + } + else + asc = (order==1); + + int prevSortOrder = currentLocalSortingOrder(); + const int prevSortColumn = currentLocalSortingOrder(); + setSorting( col, asc ); + //-perform sorting + if (!sort()) + setLocalSortingOrder(prevSortColumn, prevSortOrder); //this will also remove indicator + //if prevSortColumn==-1 + if (col != prevSortColumn) + /*emit*/ sortedColumnChanged(col); +} + +bool KexiDataAwareObjectInterface::isInsertingEnabled() const +{ + if (isReadOnly()) + return false; + if (m_insertingEnabled == 1 || m_insertingEnabled == 0) + return (bool)m_insertingEnabled; + if (!hasData()) + return true; + return m_data->isInsertingEnabled(); +} + +void KexiDataAwareObjectInterface::setFilteringEnabled(bool set) +{ + m_isFilteringEnabled = set; +} + +bool KexiDataAwareObjectInterface::isDeleteEnabled() const +{ + return (m_deletionPolicy != NoDelete) && !isReadOnly(); +} + +void KexiDataAwareObjectInterface::setDeletionPolicy(DeletionPolicy policy) +{ + m_deletionPolicy = policy; +// updateContextMenu(); +} + +void KexiDataAwareObjectInterface::setReadOnly(bool set) +{ + if (isReadOnly() == set || (m_data && m_data->isReadOnly() && !set)) + return; //not allowed! + m_readOnly = (set ? 1 : 0); + if (set) + setInsertingEnabled(false); + updateWidgetContents(); + /*emit*/ reloadActions(); +} + +bool KexiDataAwareObjectInterface::isReadOnly() const +{ + if (!hasData()) + return true; + if (m_readOnly == 1 || m_readOnly == 0) + return (bool)m_readOnly; + if (!hasData()) + return true; + return m_data->isReadOnly(); +} + +void KexiDataAwareObjectInterface::setInsertingEnabled(bool set) +{ + if (isInsertingEnabled() == set || (m_data && !m_data->isInsertingEnabled() && set)) + return; //not allowed! + m_insertingEnabled = (set ? 1 : 0); + m_navPanel->setInsertingEnabled(set); + if (m_verticalHeader) + m_verticalHeader->showInsertRow(set); + if (set) + setReadOnly(false); +// update(); + updateWidgetContents(); + /*emit*/ reloadActions(); +} + +void KexiDataAwareObjectInterface::setSpreadSheetMode() +{ + m_spreadSheetMode = true; + setSortingEnabled( false ); + setInsertingEnabled( false ); + setAcceptsRowEditAfterCellAccepting( true ); + setFilteringEnabled( false ); + setEmptyRowInsertingEnabled( true ); + m_navPanelEnabled = false; +} + +void KexiDataAwareObjectInterface::selectNextRow() +{ + selectRow( QMIN( rows() - 1 +(isInsertingEnabled()?1:0), m_curRow + 1 ) ); +} + +void KexiDataAwareObjectInterface::selectPrevPage() +{ + selectRow( + QMAX( 0, m_curRow - rowsPerPage() ) + ); +} + +void KexiDataAwareObjectInterface::selectNextPage() +{ + selectRow( + QMIN( + rows() - 1 + (isInsertingEnabled()?1:0), + m_curRow + rowsPerPage() + ) + ); +} + +void KexiDataAwareObjectInterface::selectFirstRow() +{ + selectRow(0); +} + +void KexiDataAwareObjectInterface::selectLastRow() +{ +// selectRow(rows() - 1 + (isInsertingEnabled()?1:0)); + selectRow(rows() - 1); +} + +void KexiDataAwareObjectInterface::selectRow(int row) +{ + m_vScrollBarValueChanged_enabled = false; //disable tooltip + setCursorPosition(row, -1); + m_vScrollBarValueChanged_enabled = true; +} + +void KexiDataAwareObjectInterface::selectPrevRow() +{ + selectRow( QMAX( 0, m_curRow - 1 ) ); +} + +void KexiDataAwareObjectInterface::clearSelection() +{ +// selectRow( -1 ); + int oldRow = m_curRow; +// int oldCol = m_curCol; + m_curRow = -1; + m_curCol = -1; + m_currentItem = 0; + updateRow( oldRow ); + m_navPanel->setCurrentRecordNumber(0); +// setNavRowNumber(-1); +} + +void KexiDataAwareObjectInterface::setCursorPosition(int row, int col/*=-1*/, bool forceSet) +{ + int newrow = row; + int newcol = col; + + if(rows() <= 0) { + if (m_verticalHeader) + m_verticalHeader->setCurrentRow(-1); + if (m_horizontalHeader) + m_horizontalHeader->setSelectedSection(-1); + if (isInsertingEnabled()) { + m_currentItem=m_insertItem; + newrow=0; + if (col>=0) + newcol=col; + else + newcol=0; + } + else { + m_currentItem=0; + m_curRow=-1; + m_curCol=-1; + return; + } + } + + if(col>=0) + { + newcol = QMAX(0, col); + newcol = QMIN(columns() - 1, newcol); + } + else { + newcol = m_curCol; //no changes + newcol = QMAX(0, newcol); //may not be < 0 ! + } + newrow = QMAX(0, row); + newrow = QMIN(rows() - 1 + (isInsertingEnabled()?1:0), newrow); + +// d->pCurrentItem = itemAt(d->curRow); +// kdDebug(44021) << "setCursorPosition(): d->curRow=" << d->curRow << " oldRow=" << oldRow << " d->curCol=" << d->curCol << " oldCol=" << oldCol << endl; + + if ( forceSet || m_curRow != newrow || m_curCol != newcol ) + { + kexidbg << "setCursorPosition(): " <<QString("old:%1,%2 new:%3,%4").arg(m_curCol) + .arg(m_curRow).arg(newcol).arg(newrow) << endl; + + // cursor moved: get rid of editor + if (m_editor) { + if (!m_contentsMousePressEvent_dblClick) { + if (!acceptEditor()) { + return; + } + //update row num. again + newrow = QMIN( rows() - 1 + (isInsertingEnabled()?1:0), newrow); + } + } + if (m_errorMessagePopup) { + m_errorMessagePopup->close(); + } + + if (m_curRow != newrow || forceSet) {//update current row info + m_navPanel->setCurrentRecordNumber(newrow+1); +// setNavRowNumber(newrow); +// d->navBtnPrev->setEnabled(newrow>0); +// d->navBtnFirst->setEnabled(newrow>0); +// d->navBtnNext->setEnabled(newrow<(rows()-1+(isInsertingEnabled()?1:0))); +// d->navBtnLast->setEnabled(newrow!=(rows()-1)); + } + + // cursor moved to other row: end of row editing + bool newRowInserted = false; + if (m_rowEditing && m_curRow != newrow) { + newRowInserted = m_newRowEditing; + if (!acceptRowEdit()) { + //accepting failed: cancel setting the cursor + return; + } + //update row number, because number of rows changed + newrow = QMIN( rows() - 1 + (isInsertingEnabled()?1:0), newrow); + + m_navPanel->setCurrentRecordNumber(newrow+1); //refresh + } + + //change position + int oldRow = m_curRow; + int oldCol = m_curCol; + m_curRow = newrow; + m_curCol = newcol; + +// int cw = columnWidth( d->curCol ); +// int rh = rowHeight(); +// ensureVisible( columnPos( d->curCol ) + cw / 2, rowPos( d->curRow ) + rh / 2, cw / 2, rh / 2 ); +// center(columnPos(d->curCol) + cw / 2, rowPos(d->curRow) + rh / 2, cw / 2, rh / 2); +// kdDebug(44021) << " contentsY() = "<< contentsY() << endl; + +//js if (oldRow > d->curRow) +//js ensureVisible(columnPos(d->curCol), rowPos(d->curRow) + rh, columnWidth(d->curCol), rh); +//js else// if (oldRow <= d->curRow) +//js ensureVisible(columnPos(d->curCol), rowPos(d->curRow), columnWidth(d->curCol), rh); + + + //show editor-dependent focus, if we're changing the current column + if (oldCol>=0 && oldCol<columns() && m_curCol!=oldCol) { + //find the editor for this column + KexiDataItemInterface *edit = editor( oldCol ); + if (edit) { + edit->hideFocus(); + } + } + + // position changed, so subsequent searching should be started from scratch + // (e.g. from the current cell or the top-left cell) + m_positionOfRecentlyFoundValue.exists = false; + + //show editor-dependent focus, if needed + editorShowFocus( m_curRow, m_curCol ); + + if (m_updateEntireRowWhenMovingToOtherRow) + updateRow( oldRow ); + else + updateCell( oldRow, oldCol ); + +// //quite clever: ensure the cell is visible: +// ensureCellVisible(m_curRow, m_curCol); + +// QPoint pcenter = QRect( columnPos(d->curCol), rowPos(d->curRow), columnWidth(d->curCol), rh).center(); +// ensureVisible(pcenter.x(), pcenter.y(), columnWidth(d->curCol)/2, rh/2); + +// ensureVisible(columnPos(d->curCol), rowPos(d->curRow) - contentsY(), columnWidth(d->curCol), rh); + if (m_verticalHeader && (oldRow != m_curRow || forceSet)) + m_verticalHeader->setCurrentRow(m_curRow); + + if (m_updateEntireRowWhenMovingToOtherRow) + updateRow( m_curRow ); + else + updateCell( m_curRow, m_curCol ); + + if (m_curCol != oldCol || m_curRow != oldRow || forceSet) {//ensure this is also refreshed + if (!m_updateEntireRowWhenMovingToOtherRow) //only if entire row has not been updated + updateCell( oldRow, m_curCol ); + } + //update row + if (forceSet || m_curRow != oldRow) { + if (isInsertingEnabled() && m_curRow == rows()) { + kdDebug(44021) << "NOW insert item is current" << endl; + m_currentItem = m_insertItem; + } + else { + kdDebug(44021) << QString("NOW item at %1 (%2) is current") + .arg(m_curRow).arg((ulong)itemAt(m_curRow)) << endl; + //NOT EFFECTIVE!!!!!!!!!!! + //set item iterator + if (!newRowInserted && isInsertingEnabled() && m_currentItem == m_insertItem && m_curRow == (rows()-1)) { + //moving from 'insert item' to last item + m_itemIterator->toLast(); + } + else if (!newRowInserted && !forceSet && m_currentItem != m_insertItem && 0==m_curRow) + m_itemIterator->toFirst(); + else if (!newRowInserted && !forceSet && m_currentItem != m_insertItem && oldRow>=0 && (oldRow+1)==m_curRow) //just move next + ++(*m_itemIterator); + else if (!newRowInserted && !forceSet && m_currentItem != m_insertItem && oldRow>=0 && (oldRow-1)==m_curRow) //just move back + --(*m_itemIterator); + else { //move at: + m_itemIterator->toFirst(); + (*m_itemIterator)+=m_curRow; + } + if (!**m_itemIterator) { //sanity + m_itemIterator->toFirst(); + (*m_itemIterator)+=m_curRow; + } + m_currentItem = **m_itemIterator; + //itemAt(m_curRow); + } + } + + //quite clever: ensure the cell is visible: + ensureCellVisible(m_curRow, m_curCol); + + if (m_horizontalHeader && (oldCol != m_curCol || forceSet)) + m_horizontalHeader->setSelectedSection(m_curCol); + + /*emit*/ itemSelected(m_currentItem); + /*emit*/ cellSelected(m_curCol, m_curRow); + /* only needed for forms */ + selectCellInternal(); + } + else { + kexidbg << "setCursorPosition(): NO CHANGE" << endl; + } + + if(m_initDataContentsOnShow) { + m_cursorPositionSetExplicityBeforeShow = true; + } +} + +bool KexiDataAwareObjectInterface::acceptRowEdit() +{ + if (!m_rowEditing || /*sanity*/!m_data->rowEditBuffer()) + return true; + if (m_inside_acceptEditor) { + m_internal_acceptsRowEditAfterCellAccepting = true; + return true; + } + m_internal_acceptsRowEditAfterCellAccepting = false; + + const int columnEditedBeforeAccepting = m_editor ? currentColumn() : -1; + if (!acceptEditor()) + return false; + kdDebug() << "EDIT ROW ACCEPTING..." << endl; + + bool success = true; +// bool allow = true; +// int faultyColumn = -1; // will be !=-1 if cursor has to be moved to that column + const bool inserting = m_newRowEditing; +// QString msg, desc; +// bool inserting = d->pInsertItem && d->pInsertItem==d->pCurrentItem; + + if (m_data->rowEditBuffer()->isEmpty() && !m_newRowEditing) { +/* if (d->newRowEditing) { + cancelRowEdit(); + kdDebug() << "-- NOTHING TO INSERT!!!" << endl; + return true; + } + else {*/ + kdDebug() << "-- NOTHING TO ACCEPT!!!" << endl; +// } + } + else {//not empty edit buffer or new row to insert: + if (m_newRowEditing) { +// emit aboutToInsertRow(d->pCurrentItem, m_data->rowEditBuffer(), success, &faultyColumn); +// if (success) { + kdDebug() << "-- INSERTING: " << endl; + m_data->rowEditBuffer()->debug(); + success = m_data->saveNewRow(*m_currentItem); +// if (!success) { +// } +// } + } + else { +// emit aboutToUpdateRow(d->pCurrentItem, m_data->rowEditBuffer(), success, &faultyColumn); + if (success) { + //accept changes for this row: + kdDebug() << "-- UPDATING: " << endl; + m_data->rowEditBuffer()->debug(); + kdDebug() << "-- BEFORE: " << endl; + m_currentItem->debug(); + success = m_data->saveRowChanges(*m_currentItem);//, &msg, &desc, &faultyColumn); + kdDebug() << "-- AFTER: " << endl; + m_currentItem->debug(); + +// if (!success) { +// } + } + } + } + + if (success) { + //editing is finished: + if (m_newRowEditing) { + //update current-item-iterator + m_itemIterator->toLast(); + m_currentItem = **m_itemIterator; + } + m_rowEditing = false; + m_newRowEditing = false; + //indicate on the vheader that we are not editing + if (m_verticalHeader) + m_verticalHeader->setEditRow(-1); + + updateAfterAcceptRowEdit(); + + kdDebug() << "EDIT ROW ACCEPTED:" << endl; +// /*debug*/itemAt(m_curRow); + + if (inserting) { +// emit rowInserted(d->pCurrentItem); + //update navigator's data + m_navPanel->setRecordCount(rows()); + } + else { +// emit rowUpdated(d->pCurrentItem); + } + + /*emit*/ rowEditTerminated(m_curRow); + } + else { +// if (!allow) { +// kdDebug() << "INSERT/EDIT ROW - DISALLOWED by signal!" << endl; +// } +// else { +// kdDebug() << "EDIT ROW - ERROR!" << endl; +// } + int faultyColumn = -1; + if (m_data->result()->column >= 0 && m_data->result()->column < columns()) + faultyColumn = m_data->result()->column; + else if (columnEditedBeforeAccepting >= 0) + faultyColumn = columnEditedBeforeAccepting; + if (faultyColumn >= 0) { + setCursorPosition(m_curRow, faultyColumn); + } + + const int button = showErrorMessageForResult( m_data->result() ); + if (KMessageBox::No == button) { + //discard changes + cancelRowEdit(); + } + else { + if (faultyColumn >= 0) { + //edit this cell + startEditCurrentCell(); + } + } + } + + return success; +} + +bool KexiDataAwareObjectInterface::cancelRowEdit() +{ + if (!hasData()) + return false; + if (!m_rowEditing) + return false; + cancelEditor(); + m_rowEditing = false; + //indicate on the vheader that we are not editing + if (m_verticalHeader) + m_verticalHeader->setEditRow(-1); + m_alsoUpdateNextRow = m_newRowEditing; + if (m_newRowEditing) { + m_newRowEditing = false; + //remove current edited row (it is @ the end of list) + m_data->removeLast(); + //current item is now empty, last row + m_currentItem = m_insertItem; + //update visibility + if (m_verticalHeader) + m_verticalHeader->removeLabel(false); //-1 label +// updateContents(columnPos(0), rowPos(rows()), +// viewport()->width(), d->rowHeight*3 + (m_navPanel ? m_navPanel->height() : 0)*3 ); +// updateContents(); //js: above did not work well so we do that dirty + updateWidgetContents(); +//TODO: still doesn't repaint properly!! +// QSize s(tableSize()); +// resizeContents(s.width(), s.height()); + updateWidgetContentsSize(); +// m_verticalHeader->update(); + //--no cancel action is needed for datasource, + // because the row was not yet stored. + } + + m_data->clearRowEditBuffer(); + updateAfterCancelRowEdit(); + +//! \todo (js): cancel changes for this row! + kexidbg << "EDIT ROW CANCELLED." << endl; + + /*emit*/ rowEditTerminated(m_curRow); + return true; +} + +void KexiDataAwareObjectInterface::updateAfterCancelRowEdit() +{ + updateRow(m_curRow); + if (m_alsoUpdateNextRow) + updateRow(m_curRow+1); + m_alsoUpdateNextRow = false; +} + +void KexiDataAwareObjectInterface::updateAfterAcceptRowEdit() +{ + updateRow(m_curRow); +} + +void KexiDataAwareObjectInterface::removeEditor() +{ + if (!m_editor) + return; + m_editor->hideWidget(); + m_editor = 0; +} + +bool KexiDataAwareObjectInterface::cancelEditor() +{ + if (m_errorMessagePopup) { + m_errorMessagePopup->close(); + } + if (!m_editor) + return false; + removeEditor(); + return true; +} + +//! @internal +class KexiDataAwareObjectInterfaceToolTip : public QToolTip { + public: + KexiDataAwareObjectInterfaceToolTip( const QString & text, const QPoint & pos, QWidget * widget ) + : QToolTip(widget), m_text(text) + { + tip( QRect(pos, QSize(100, 100)), text ); + } + virtual void maybeTip(const QPoint & p) { + tip( QRect(p, QSize(100, 100)), m_text); + } + QString m_text; +}; + +bool KexiDataAwareObjectInterface::acceptEditor() +{ + if (!hasData()) + return true; + if (!m_editor || m_inside_acceptEditor) + return true; + + m_inside_acceptEditor = true;//avoid recursion + + QVariant newval; + Validator::Result res = Validator::Ok; + QString msg, desc; + bool setNull = false; +// bool allow = true; +// static const QString msg_NOT_NULL = i18n("\"%1\" column requires a value to be entered."); + + //autoincremented field can be omitted (left as null or empty) if we're inserting a new row + const bool autoIncColumnCanBeOmitted = m_newRowEditing && m_editor->field()->isAutoIncrement(); +// const bool autoIncColumnCanBeOmitted = m_newRowEditing && m_editor->columnInfo()->field->isAutoIncrement(); + + bool valueChanged = m_editor->valueChanged(); + bool editCurrentCellAgain = false; + + if (valueChanged) { + if (!m_editor->valueIsValid()) { + //used e.g. for date or time values - the value can be null but not necessary invalid + res = Validator::Error; + editCurrentCellAgain = true; + QWidget *par = dynamic_cast<QScrollView*>(this) ? dynamic_cast<QScrollView*>(this)->viewport() : + dynamic_cast<QWidget*>(this); + QWidget *edit = dynamic_cast<QWidget*>(m_editor); + if (par && edit) { +//! @todo allow displaying user-defined warning +//! @todo also use for other error messages + if (!m_errorMessagePopup) { +// m_errorMessagePopup->close(); + m_errorMessagePopup = new KexiArrowTip( + i18n("Error: %1").arg(m_editor->columnInfo()->field->typeName())+"?", + dynamic_cast<QWidget*>(this)); + m_errorMessagePopup->move( + par->mapToGlobal(edit->pos()) + QPoint(6, edit->height() + 0) ); + m_errorMessagePopup->show(); + } + m_editor->setFocus(); + } + } + else if (m_editor->valueIsNull()) {//null value entered +// if (m_editor->columnInfo()->field->isNotNull() && !autoIncColumnCanBeOmitted) { + if (m_editor->field()->isNotNull() && !autoIncColumnCanBeOmitted) { + kdDebug() << "KexiDataAwareObjectInterface::acceptEditor(): NULL NOT ALLOWED!" << endl; + res = Validator::Error; +// msg = Validator::msgColumnNotEmpty().arg(m_editor->columnInfo()->field->captionOrName()) + msg = Validator::msgColumnNotEmpty().arg(m_editor->field()->captionOrName()) + + "\n\n" + Kexi::msgYouCanImproveData(); + desc = i18n("The column's constraint is declared as NOT NULL."); + editCurrentCellAgain = true; + // allow = false; + // removeEditor(); + // return true; + } + else { + kdDebug() << "KexiDataAwareObjectInterface::acceptEditor(): NULL VALUE WILL BE SET" << endl; + //ok, just leave newval as NULL + setNull = true; + } + } + else if (m_editor->valueIsEmpty()) {//empty value entered +// if (m_editor->columnInfo()->field->hasEmptyProperty()) { + if (m_editor->field()->hasEmptyProperty()) { +// if (m_editor->columnInfo()->field->isNotEmpty() && !autoIncColumnCanBeOmitted) { + if (m_editor->field()->isNotEmpty() && !autoIncColumnCanBeOmitted) { + kdDebug() << "KexiDataAwareObjectInterface::acceptEditor(): EMPTY NOT ALLOWED!" << endl; + res = Validator::Error; +// msg = Validator::msgColumnNotEmpty().arg(m_editor->columnInfo()->field->captionOrName()) + msg = Validator::msgColumnNotEmpty().arg(m_editor->field()->captionOrName()) + + "\n\n" + Kexi::msgYouCanImproveData(); + desc = i18n("The column's constraint is declared as NOT EMPTY."); + editCurrentCellAgain = true; + // allow = false; + // removeEditor(); + // return true; + } + else { + kdDebug() << "KexiDataAwareObjectInterface::acceptEditor(): EMPTY VALUE WILL BE SET" << endl; + } + } + else { +// if (m_editor->columnInfo()->field->isNotNull() && !autoIncColumnCanBeOmitted) { + if (m_editor->field()->isNotNull() && !autoIncColumnCanBeOmitted) { + kdDebug() << "KexiDataAwareObjectInterface::acceptEditor(): NEITHER NULL NOR EMPTY VALUE CAN BE SET!" << endl; + res = Validator::Error; +// msg = Validator::msgColumnNotEmpty().arg(m_editor->columnInfo()->field->captionOrName()) + msg = Validator::msgColumnNotEmpty().arg(m_editor->field()->captionOrName()) + + "\n\n" + Kexi::msgYouCanImproveData(); + desc = i18n("The column's constraint is declared as NOT EMPTY and NOT NULL."); + editCurrentCellAgain = true; +// allow = false; + // removeEditor(); + // return true; + } + else { + kdDebug() << "KexiDataAwareObjectInterface::acceptEditor(): NULL VALUE WILL BE SET BECAUSE EMPTY IS NOT ALLOWED" << endl; + //ok, just leave newval as NULL + setNull = true; + } + } + } + }//changed + + const int realFieldNumber = fieldNumberForColumn(m_curCol); + if (realFieldNumber < 0) { + kdWarning() << "KexiDataAwareObjectInterface::acceptEditor(): fieldNumberForColumn(m_curCol) < 0" << endl; + m_inside_acceptEditor = false; + return false; + } + + KexiTableViewColumn *currentTVColumn = column(m_curCol); + + //try to get the value entered: + if (res == Validator::Ok) { + if ((!setNull && !valueChanged) + || (m_editor->field()->type()!=KexiDB::Field::Boolean && setNull && m_currentItem->at( realFieldNumber ).isNull())) { + kdDebug() << "KexiDataAwareObjectInterface::acceptEditor(): VALUE NOT CHANGED." << endl; + removeEditor(); + if (m_acceptsRowEditAfterCellAccepting || m_internal_acceptsRowEditAfterCellAccepting) + acceptRowEdit(); + m_inside_acceptEditor = false; + return true; + } + if (!setNull) {//get the new value +// bool ok; + newval = m_editor->value(); +//! @todo validation rules for this value? +/* + if (!ok) { + kdDebug() << "KexiDataAwareObjectInterface::acceptEditor(): INVALID VALUE - NOT CHANGED." << endl; + res = KexiValidator::Error; +//js: TODO get detailed info on why m_editor->value() failed + msg = i18n("Entered value is invalid.") + + "\n\n" + KexiValidator::msgYouCanImproveData(); + editCurrentCellAgain = true; +// removeEditor(); +// return true; + }*/ + } + + //Check other validation rules: + //1. check using validator +// KexiValidator *validator = m_data->column(m_curCol)->validator(); + Validator *validator = currentTVColumn->validator(); + if (validator) { +// res = validator->check(m_data->column(m_curCol)->field()->captionOrName(), + res = validator->check(currentTVColumn->field()->captionOrName(), + newval, msg, desc); + } + } + + //show the validation result if not OK: + if (res == Validator::Error) { + if (!msg.isEmpty()) { + if (desc.isEmpty()) + KMessageBox::sorry(dynamic_cast<QWidget*>(this), msg); + else + KMessageBox::detailedSorry(dynamic_cast<QWidget*>(this), msg, desc); + } + editCurrentCellAgain = true; +// allow = false; + } + else if (res == Validator::Warning) { + //js: todo: message!!! + KMessageBox::messageBox(dynamic_cast<QWidget*>(this), KMessageBox::Sorry, msg + "\n" + desc); + editCurrentCellAgain = true; + } + + if (res == Validator::Ok) { + //2. check using signal + //bool allow = true; +// emit aboutToChangeCell(d->pCurrentItem, newval, allow); +// if (allow) { + //send changes to the backend + QVariant visibleValue; + if (!newval.isNull()/* visible value should be null if value is null */ + && currentTVColumn->visibleLookupColumnInfo) + { + visibleValue = m_editor->visibleValue(); //visible value for lookup field + } + //should be also added to the buffer + if (m_data->updateRowEditBufferRef(m_currentItem, m_curCol, currentTVColumn, + newval, /*allowSignals*/true, currentTVColumn->visibleLookupColumnInfo ? &visibleValue : 0)) + { + kdDebug() << "KexiDataAwareObjectInterface::acceptEditor(): ------ EDIT BUFFER CHANGED TO:" << endl; + m_data->rowEditBuffer()->debug(); + } else { + kdDebug() << "KexiDataAwareObjectInterface::acceptEditor(): ------ CHANGE FAILED in KexiDataAwareObjectInterface::updateRowEditBuffer()" << endl; + res = Validator::Error; + + //now: there might be called cancelEditor() in updateRowEditBuffer() handler, + //if this is true, d->pEditor is NULL. + + if (m_editor && m_data->result()->column>=0 && m_data->result()->column<columns()) { + //move to faulty column (if m_editor is not cleared) + setCursorPosition(m_curRow, m_data->result()->column); + } + if (!m_data->result()->msg.isEmpty()) { + const int button = showErrorMessageForResult( m_data->result() ); + if (KMessageBox::No == button) { + //discard changes + cancelEditor(); + if (m_acceptsRowEditAfterCellAccepting) + cancelRowEdit(); + m_inside_acceptEditor = false; + return false; + } + } + } + } + + if (res == Validator::Ok) { + removeEditor(); + /*emit*/ itemChanged(m_currentItem, m_curRow, m_curCol, + m_currentItem->at( realFieldNumber )); + /*emit*/ itemChanged(m_currentItem, m_curRow, m_curCol); + } + m_inside_acceptEditor = false; + if (res == Validator::Ok) { + if (m_acceptsRowEditAfterCellAccepting || m_internal_acceptsRowEditAfterCellAccepting) + acceptRowEdit(); + return true; + } + if (m_editor) { + //allow to edit the cell again, (if m_pEditor is not cleared) + + if (m_editor->hasFocusableWidget()) { + m_editor->showWidget(); + m_editor->setFocus(); + } +// startEditCurrentCell(newval.type()==QVariant::String ? newval.toString() : QString::null); +// m_editor->setFocus(); + } + return false; +} + +void KexiDataAwareObjectInterface::startEditCurrentCell(const QString &setText) +{ + kdDebug() << "** KexiDataAwareObjectInterface::startEditCurrentCell("<<setText<<")"<<endl; +// if (columnType(d->curCol) == KexiDB::Field::Boolean) +// return; + if (isReadOnly() || !columnEditable(m_curCol)) + return; + if (m_editor) { + if (m_editor->hasFocusableWidget()) { + m_editor->showWidget(); + m_editor->setFocus(); + } + } +// ensureVisible(columnPos(m_curCol), rowPos(m_curRow)+rowHeight(), +// columnWidth(m_curCol), rowHeight()); +//OK? + //ensureCellVisible(m_curRow+1, m_curCol); + if (!m_editor) + createEditor(m_curRow, m_curCol, setText, !setText.isEmpty()); +} + +void KexiDataAwareObjectInterface::deleteAndStartEditCurrentCell() +{ + if (isReadOnly() || !columnEditable(m_curCol)) + return; + if (m_editor) {//if we've editor - just clear it + m_editor->clear(); + return; + } +//js if (columnType(m_curCol) == KexiDB::Field::Boolean) +//js return; +// ensureVisible(columnPos(m_curCol), rowPos(m_curRow) + rowHeight(), +// columnWidth(m_curCol), rowHeight()); +//OK? + ensureCellVisible(m_curRow+1, m_curCol); + createEditor(m_curRow, m_curCol, QString::null, false/*removeOld*/); + if (!m_editor) + return; + m_editor->clear(); + if (m_editor->acceptEditorAfterDeleteContents()) + acceptEditor(); + if (!m_editor || !m_editor->hasFocusableWidget()) + updateCell(m_curRow, m_curCol); +} + +void KexiDataAwareObjectInterface::deleteCurrentRow() +{ + if (m_newRowEditing) {//we're editing fresh new row: just cancel this! + cancelRowEdit(); + return; + } + + if (!acceptRowEdit()) + return; + + if (!isDeleteEnabled() || !m_currentItem || m_currentItem == m_insertItem) + return; + switch (m_deletionPolicy) { + case NoDelete: + return; + case ImmediateDelete: + break; + case AskDelete: + if (KMessageBox::Cancel == KMessageBox::warningContinueCancel(dynamic_cast<QWidget*>(this), + i18n("Do you want to delete selected row?"), 0, + KGuiItem(i18n("&Delete Row"),"editdelete"), + "dontAskBeforeDeleteRow"/*config entry*/, + KMessageBox::Notify|KMessageBox::Dangerous)) + return; + break; + case SignalDelete: + /*emit*/ itemDeleteRequest(m_currentItem, m_curRow, m_curCol); + /*emit*/ currentItemDeleteRequest(); + return; + default: + return; + } + + if (!deleteItem(m_currentItem)) {//nothing + } +} + +KexiTableItem *KexiDataAwareObjectInterface::insertEmptyRow(int row) +{ + if ( !acceptRowEdit() || !isEmptyRowInsertingEnabled() + || (row!=-1 && row >= ((int)rows()+(isInsertingEnabled()?1:0) ) ) ) + return 0; + + KexiTableItem *newItem = m_data->createItem(); //new KexiTableItem(m_data->columns.count()); + insertItem(newItem, row); + return newItem; +} + +void KexiDataAwareObjectInterface::insertItem(KexiTableItem *newItem, int row) +{ + const bool changeCurrentRow = row==-1 || row==m_curRow; + if (changeCurrentRow) { + //change current row + row = (m_curRow >= 0 ? m_curRow : 0); + m_currentItem = newItem; + m_curRow = row; + } + else if (m_curRow >= row) { + m_curRow++; + } + + m_data->insertRow(*newItem, row, true /*repaint*/); + + if (changeCurrentRow) { + //update iter... + m_itemIterator->toFirst(); + (*m_itemIterator)+=m_curRow; + } +/* + QSize s(tableSize()); + resizeContents(s.width(),s.height()); + + //redraw only this row and below: + int leftcol = d->pTopHeader->sectionAt( d->pTopHeader->offset() ); +// updateContents( columnPos( leftcol ), rowPos(d->curRow), +// clipper()->width(), clipper()->height() - (rowPos(d->curRow) - contentsY()) ); + updateContents( columnPos( leftcol ), rowPos(row), + clipper()->width(), clipper()->height() - (rowPos(row) - contentsY()) ); + + m_verticalHeader->addLabel(); + + //update navigator's data + setNavRowCount(rows()); + + if (d->curRow >= row) { + //update + editorShowFocus( d->curRow, d->curCol ); + } + */ +} + +void KexiDataAwareObjectInterface::slotRowInserted(KexiTableItem *item, bool repaint) +{ + int row = m_data->findRef(item); + slotRowInserted( item, row, repaint ); +} + +void KexiDataAwareObjectInterface::slotRowInserted(KexiTableItem * /*item*/, uint row, bool repaint) +{ + if (repaint && (int)row<rows()) { + updateWidgetContentsSize(); + +/* updateAllVisibleRowsBelow() used instead + //redraw only this row and below: + int leftcol = d->pTopHeader->sectionAt( d->pTopHeader->offset() ); + updateContents( columnPos( leftcol ), rowPos(row), + clipper()->width(), clipper()->height() - (rowPos(row) - contentsY()) ); +*/ + updateAllVisibleRowsBelow(row); + + if (!m_verticalHeaderAlreadyAdded) { + if (m_verticalHeader) + m_verticalHeader->addLabel(); + } + else //it was added because this inserting was interactive + m_verticalHeaderAlreadyAdded = false; + + //update navigator's data + m_navPanel->setRecordCount(rows()); + + if (m_curRow >= (int)row) { + //update + editorShowFocus( m_curRow, m_curCol ); + } + } +} + +tristate KexiDataAwareObjectInterface::deleteAllRows(bool ask, bool repaint) +{ + if (!hasData()) + return true; + if (m_data->count()<1) + return true; + + if (ask) { + QString tableName = m_data->dbTableName(); + if (!tableName.isEmpty()) { + tableName.prepend(" \""); + tableName.append("\""); + } + if (KMessageBox::Cancel == KMessageBox::warningContinueCancel(dynamic_cast<QWidget*>(this), + i18n("Do you want to clear the contents of table %1?").arg(tableName), + 0, KGuiItem(i18n("&Clear Contents")) )) + return cancelled; + } + + cancelRowEdit(); +// acceptRowEdit(); +// m_verticalHeader->clear(); + const bool repaintLater = repaint && m_spreadSheetMode; + const int oldRows = rows(); + + bool res = m_data->deleteAllRows(repaint && !repaintLater); + + if (res) { + if (m_spreadSheetMode) { +// const uint columns = m_data->columns.count(); + for (int i=0; i<oldRows; i++) { + m_data->append(m_data->createItem());//new KexiTableItem(columns)); + } + } + } + if (repaintLater) + m_data->reload(); + +// d->clearVariables(); +// m_verticalHeader->setCurrentRow(-1); + +// d->pUpdateTimer->start(1,true); +// if (repaint) +// viewport()->repaint(); + return res; +} + +void KexiDataAwareObjectInterface::clearColumns(bool repaint) +{ + cancelRowEdit(); + m_data->clearInternal(); + + clearColumnsInternal(repaint); + updateIndicesForVisibleValues(); + + if (repaint) +// viewport()->repaint(); +//OK? + updateWidgetContents(); + +/* for(int i=0; i < rows(); i++) + { + m_verticalHeader->removeLabel(); + } + + editorCancel(); + m_contents->clear(); + + d->clearVariables(); + d->numCols = 0; + + while(d->pTopHeader->count()>0) + d->pTopHeader->removeLabel(0); + + m_verticalHeader->setCurrentRow(-1); + + viewport()->repaint(); + +// d->pColumnTypes.resize(0); +// d->pColumnModes.resize(0); +// d->pColumnDefaults.clear();*/ +} + +void KexiDataAwareObjectInterface::reloadData() +{ +// cancelRowEdit(); + acceptRowEdit(); + if (m_verticalHeader) + m_verticalHeader->clear(); + + if (m_curCol>=0 && m_curCol<columns()) { + //find the editor for this column + KexiDataItemInterface *edit = editor( m_curCol ); + if (edit) { + edit->hideFocus(); + } + } +// setCursorPosition(-1, -1, true); + clearVariables(); + if (m_verticalHeader) + m_verticalHeader->setCurrentRow(-1); + + if (dynamic_cast<QWidget*>(this) && dynamic_cast<QWidget*>(this)->isVisible()) + initDataContents(); + else + m_initDataContentsOnShow = true; + + if (m_verticalHeader) + m_verticalHeader->addLabels(m_data->count()); + + updateWidgetScrollBars(); +} + +int KexiDataAwareObjectInterface::columnType(int col) +{ + KexiTableViewColumn* c = m_data ? column(col) : 0; + return c ? c->field()->type() : KexiDB::Field::InvalidType; +} + +bool KexiDataAwareObjectInterface::columnEditable(int col) +{ + KexiTableViewColumn* c = m_data ? column(col) : 0; + return c ? (! c->isReadOnly()) : false; +} + +int KexiDataAwareObjectInterface::rows() const +{ + if (!hasData()) + return 0; + return m_data->count(); +} + +int KexiDataAwareObjectInterface::dataColumns() const +{ + if (!hasData()) + return 0; + return m_data->columns.count(); +} + +QVariant KexiDataAwareObjectInterface::columnDefaultValue(int /*col*/) const +{ + return QVariant(0); +//TODO(js) +// return m_data->columns[col].defaultValue; +} + +void KexiDataAwareObjectInterface::setAcceptsRowEditAfterCellAccepting(bool set) +{ + m_acceptsRowEditAfterCellAccepting = set; +} + +void KexiDataAwareObjectInterface::setDropsAtRowEnabled(bool set) +{ +// const bool old = d->dropsAtRowEnabled; + if (!set) + m_dragIndicatorLine = -1; + if (m_dropsAtRowEnabled && !set) { + m_dropsAtRowEnabled = false; +// update(); + updateWidgetContents(); + } + else { + m_dropsAtRowEnabled = set; + } +} + +void KexiDataAwareObjectInterface::setEmptyRowInsertingEnabled(bool set) +{ + m_emptyRowInsertingEnabled = set; + /*emit*/ reloadActions(); +} + +void KexiDataAwareObjectInterface::slotAboutToDeleteRow(KexiTableItem& item, + KexiDB::ResultInfo* /*result*/, bool repaint) +{ + if (repaint) { + m_rowWillBeDeleted = m_data->findRef(&item); + } +} + +void KexiDataAwareObjectInterface::slotRowDeleted() +{ + if (m_rowWillBeDeleted >= 0) { + if (m_rowWillBeDeleted > 0 && m_rowWillBeDeleted >= (rows()-1) && !m_spreadSheetMode) + m_rowWillBeDeleted = rows()-1; //move up if it's the last row + updateWidgetContentsSize(); + + if (! (m_spreadSheetMode && m_rowWillBeDeleted>=(rows()-1))) + setCursorPosition(m_rowWillBeDeleted, m_curCol, true/*forceSet*/); + if (m_verticalHeader) + m_verticalHeader->removeLabel(); + + updateAllVisibleRowsBelow(m_curRow); //needed for KexiTableView + + //update navigator's data + m_navPanel->setRecordCount(rows()); + + m_rowWillBeDeleted = -1; + } +} + +bool KexiDataAwareObjectInterface::beforeDeleteItem(KexiTableItem *) +{ + //always return + return true; +} + +bool KexiDataAwareObjectInterface::deleteItem(KexiTableItem *item)/*, bool moveCursor)*/ +{ + if (!item || !beforeDeleteItem(item)) + return false; + + QString msg, desc; +// bool current = (item == d->pCurrentItem); + const bool lastRowDeleted = m_spreadSheetMode && m_data->last() == item; //we need to know this so we + //can return to the last row + //after reinserting it + if (!m_data->deleteRow(*item, true /*repaint*/)) { + /*const int button =*/ + showErrorMessageForResult( m_data->result() ); +// if (KMessageBox::No == button) { + //discard changes + // } + return false; + } + else { +//setCursorPosition() wil lset this! if (current) + //d->pCurrentItem = m_data->current(); + } + +// repaintAfterDelete(); + if (m_spreadSheetMode) { //append empty row for spreadsheet mode + m_data->append(m_data->createItem());//new KexiTableItem(m_data->columns.count())); + if (m_verticalHeader) + m_verticalHeader->addLabels(1); + if (lastRowDeleted) //back to the last row + setCursorPosition(rows()-1, m_curCol, true/*forceSet*/); + /*emit*/ newItemAppendedForAfterDeletingInSpreadSheetMode(); + } + return true; +} + +KexiTableViewColumn* KexiDataAwareObjectInterface::column(int col) +{ + return m_data->column(col); +} + +bool KexiDataAwareObjectInterface::hasDefaultValueAt(const KexiTableViewColumn& tvcol) +{ + if (m_rowEditing && m_data->rowEditBuffer() && m_data->rowEditBuffer()->isDBAware()) { + return m_data->rowEditBuffer()->hasDefaultValueAt( *tvcol.columnInfo ); + } + return false; +} + +const QVariant* KexiDataAwareObjectInterface::bufferedValueAt(int col, bool useDefaultValueIfPossible) +{ + if (m_rowEditing && m_data->rowEditBuffer()) + { + KexiTableViewColumn* tvcol = column(col); + if (tvcol->isDBAware) { + //get the stored value + const int realFieldNumber = fieldNumberForColumn(col); + if (realFieldNumber < 0) { + kdWarning() << "KexiDataAwareObjectInterface::bufferedValueAt(): " + "fieldNumberForColumn(m_curCol) < 0" << endl; + return 0; + } + QVariant *storedValue = &m_currentItem->at( realFieldNumber ); + + //db-aware data: now, try to find a buffered value (or default one) + const QVariant *cv = m_data->rowEditBuffer()->at( *tvcol->columnInfo, + storedValue->isNull() && useDefaultValueIfPossible); + if (cv) + return cv; + return storedValue; + } + //not db-aware data: + const QVariant *cv = m_data->rowEditBuffer()->at( tvcol->field()->name() ); + if (cv) + return cv; + } + //not db-aware data: + const int realFieldNumber = fieldNumberForColumn(col); + if (realFieldNumber < 0) { + kdWarning() << "KexiDataAwareObjectInterface::bufferedValueAt(): " + "fieldNumberForColumn(m_curCol) < 0" << endl; + return 0; + } + return &m_currentItem->at( realFieldNumber ); +} + +void KexiDataAwareObjectInterface::startEditOrToggleValue() +{ + if ( !isReadOnly() && columnEditable(m_curCol) ) { + if (columnType(m_curCol) == KexiDB::Field::Boolean) { + boolToggled(); + } + else { + startEditCurrentCell(); + return; + } + } +} + +void KexiDataAwareObjectInterface::boolToggled() +{ + startEditCurrentCell(); + if (m_editor) { + m_editor->clickedOnContents(); + } + acceptEditor(); + updateCell(m_curRow, m_curCol); + +/* int s = m_currentItem->at(m_curCol).toInt(); + QVariant oldValue=m_currentItem->at(m_curCol); + (*m_currentItem)[m_curCol] = QVariant(s ? 0 : 1); + updateCell(m_curRow, m_curCol); +// emit itemChanged(m_currentItem, m_curRow, m_curCol, oldValue); +// emit itemChanged(m_currentItem, m_curRow, m_curCol);*/ +} + +void KexiDataAwareObjectInterface::slotDataDestroying() +{ + m_data = 0; + m_itemIterator = 0; +} + +void KexiDataAwareObjectInterface::addNewRecordRequested() +{ + if (!isInsertingEnabled()) + return; + if (m_rowEditing) { + if (!acceptRowEdit()) + return; + } +// setFocus(); + selectRow(rows()); + startEditCurrentCell(); + if (m_editor) + m_editor->setFocus(); +} + +bool KexiDataAwareObjectInterface::handleKeyPress(QKeyEvent *e, int &curRow, int &curCol, + bool fullRowSelection, bool *moveToFirstField, bool *moveToLastField) +{ + if (moveToFirstField) + *moveToFirstField = false; + if (moveToLastField) + *moveToLastField = false; + + const bool nobtn = e->state()==Qt::NoButton; + const int k = e->key(); + //kdDebug() << "-----------" << e->state() << " " << k << endl; + + if ((k == Qt::Key_Up && nobtn) || (k == Qt::Key_PageUp && e->state()==Qt::ControlButton)) { + selectPrevRow(); + e->accept(); + } + else if ((k == Qt::Key_Down && nobtn) || (k == Qt::Key_PageDown && e->state()==Qt::ControlButton)) { + selectNextRow(); + e->accept(); + } + else if (k == Qt::Key_PageUp && nobtn) { + selectPrevPage(); + e->accept(); + } + else if (k == Qt::Key_PageDown && nobtn) { + selectNextPage(); + e->accept(); + } + else if (k == Qt::Key_Home) { + if (fullRowSelection) { + //we're in row-selection mode: home key always moves to 1st row + curRow = 0;//to 1st row + } + else {//cell selection mode: different actions depending on ctrl and shift keys state + if (nobtn) { + curCol = 0;//to 1st col + } + else if (e->state()==Qt::ControlButton) { + curRow = 0;//to 1st row and col + curCol = 0; + } + else + return false; + } + if (moveToFirstField) + *moveToFirstField = true; + //do not accept yet + e->ignore(); + } + else if (k == Qt::Key_End) { + if (fullRowSelection) { + //we're in row-selection mode: home key always moves to last row + curRow = m_data->count()-1+(isInsertingEnabled()?1:0);//to last row + } + else {//cell selection mode: different actions depending on ctrl and shift keys state + if (nobtn) { + curCol = columns()-1;//to last col + } + else if (e->state()==Qt::ControlButton) { + curRow = m_data->count()-1 /*+(isInsertingEnabled()?1:0)*/; //to last row and col + curCol = columns()-1;//to last col + } + else + return false; + } + if (moveToLastField) + *moveToLastField = true; + //do not accept yet + e->ignore(); + } + else if (isInsertingEnabled() && (e->state()==Qt::ControlButton && k == Qt::Key_Equal + || e->state()==(Qt::ControlButton|Qt::ShiftButton) && k == Qt::Key_Equal)) { + curRow = m_data->count(); //to the new row + curCol = 0;//to first col + if (moveToFirstField) + *moveToFirstField = true; + //do not accept yet + e->ignore(); + } + else + return false; + + return true; +} + +void KexiDataAwareObjectInterface::vScrollBarValueChanged(int v) +{ + Q_UNUSED(v); + if (!m_vScrollBarValueChanged_enabled) + return; + + if (m_scrollbarToolTipsEnabled) { + const QRect r( verticalScrollBar()->sliderRect() ); + const int row = lastVisibleRow()+1; + if (row<=0) { + m_scrollBarTipTimer.stop(); + m_scrollBarTip->hide(); + return; + } + m_scrollBarTip->setText( i18n("Row: ") + QString::number(row) ); + m_scrollBarTip->adjustSize(); + QWidget* thisWidget = dynamic_cast<QWidget*>(this); + m_scrollBarTip->move( + thisWidget->mapToGlobal( r.topLeft() + verticalScrollBar()->pos() ) + + QPoint( - m_scrollBarTip->width()-5, + r.height()/2 - m_scrollBarTip->height()/2) ); + if (verticalScrollBar()->draggingSlider()) { + kdDebug(44021) << " draggingSlider() " << endl; + m_scrollBarTipTimer.stop(); + m_scrollBarTip->show(); + m_scrollBarTip->raise(); + } + else { + m_scrollBarTipTimerCnt++; + if (m_scrollBarTipTimerCnt>4) { + m_scrollBarTipTimerCnt=0; + m_scrollBarTip->show(); + m_scrollBarTip->raise(); + m_scrollBarTipTimer.start(500, true); + } + } + } + //update bottom view region +/* if (m_navPanel && (contentsHeight() - contentsY() - clipper()->height()) <= QMAX(d->rowHeight,m_navPanel->height())) { + slotUpdate(); + triggerUpdate(); + }*/ +} + +bool KexiDataAwareObjectInterface::scrollbarToolTipsEnabled() const +{ + return m_scrollbarToolTipsEnabled; +} + +void KexiDataAwareObjectInterface::setScrollbarToolTipsEnabled(bool set) +{ + m_scrollbarToolTipsEnabled = set; +} + +void KexiDataAwareObjectInterface::vScrollBarSliderReleased() +{ + kdDebug(44021) << "vScrollBarSliderReleased()" << endl; + m_scrollBarTip->hide(); +} + +void KexiDataAwareObjectInterface::scrollBarTipTimeout() +{ + if (m_scrollBarTip->isVisible()) { +// kdDebug(44021) << "TIMEOUT! - hide" << endl; + if (m_scrollBarTipTimerCnt>0) { + m_scrollBarTipTimerCnt=0; + m_scrollBarTipTimer.start(500, true); + return; + } + m_scrollBarTip->hide(); + } + m_scrollBarTipTimerCnt=0; +} + +void KexiDataAwareObjectInterface::focusOutEvent(QFocusEvent* e) +{ + Q_UNUSED(e); + m_scrollBarTipTimer.stop(); + m_scrollBarTip->hide(); + + updateCell(m_curRow, m_curCol); +} + +int KexiDataAwareObjectInterface::showErrorMessageForResult(KexiDB::ResultInfo* resultInfo) +{ + QWidget *thisWidget = dynamic_cast<QWidget*>(this); + if (resultInfo->allowToDiscardChanges) { + return KMessageBox::questionYesNo(thisWidget, resultInfo->msg + + (resultInfo->desc.isEmpty() ? QString::null : ("\n"+resultInfo->desc)), + QString::null, + KGuiItem(i18n("Correct Changes", "Correct"), QString::null, i18n("Correct changes")), + KGuiItem(i18n("Discard Changes")) ); + } + + if (resultInfo->desc.isEmpty()) + KMessageBox::sorry(thisWidget, resultInfo->msg); + else + KMessageBox::detailedSorry(thisWidget, resultInfo->msg, resultInfo->desc); + + return KMessageBox::Ok; +} + +void KexiDataAwareObjectInterface::updateIndicesForVisibleValues() +{ + m_indicesForVisibleValues.resize( m_data ? m_data->columnsCount() : 0 ); + if (!m_data) + return; + for (uint i=0; i < m_data->columnsCount(); i++) { + KexiTableViewColumn* tvCol = m_data->column(i); + if (tvCol->columnInfo && tvCol->columnInfo->indexForVisibleLookupValue()!=-1) + // retrieve visible value from lookup field + m_indicesForVisibleValues[ i ] = tvCol->columnInfo->indexForVisibleLookupValue(); + else + m_indicesForVisibleValues[ i ] = i; + } +} + +/*! Performs searching \a stringValue in \a where string. + \a matchAnyPartOfField, \a matchWholeField, \a wholeWordsOnly options are used to control how to search. + + If \a matchWholeField is true, \a wholeWordsOnly is not checked. + \a firstCharacter is in/out parameter. If \a matchAnyPartOfField is true and \a matchWholeField is false, + \a firstCharacter >= 0, the search will be performed after skipping first \a firstCharacter characters. + + If \a forward is false, we are searching backwart from \a firstCharacter position. \a firstCharacter == -1 + means then the last character. \a firstCharacter == INT_MAX means "before first" place, so searching fails + immediately. + On success, true is returned and \a firstCharacter is set to position of the matched string. */ +static inline bool findInString(const QString& stringValue, int stringLength, const QString& where, + int& firstCharacter, bool matchAnyPartOfField, bool matchWholeField, + bool caseSensitive, bool wholeWordsOnly, bool forward) +{ + if (where.isEmpty()) { + firstCharacter = -1; + return false; + } + + if (matchAnyPartOfField) { + if (forward) { + int pos = firstCharacter == -1 ? 0 : firstCharacter; + if (wholeWordsOnly) { + const int whereLength = where.length(); + while (true) { + pos = where.find( stringValue, pos, caseSensitive ); + if (pos == -1) + break; + if ((pos > 0 && where.at(pos-1).isLetterOrNumber()) + ||((pos+stringLength-1) < (whereLength-1) && where.at(pos+stringLength-1+1).isLetterOrNumber())) + { + pos++; // invalid match because before or after the string there is non-white space + } + else + break; + }//while + firstCharacter = pos; + } + else {// !wholeWordsOnly + firstCharacter = where.find( stringValue, pos, caseSensitive ); + } + return firstCharacter != -1; + } + else { // !matchAnyPartOfField + if (firstCharacter == INT_MAX) { + firstCharacter = -1; //next time we'll be looking at different cell + return false; + } + int pos = firstCharacter; + if (wholeWordsOnly) { + const int whereLength = where.length(); + while (true) { + pos = where.findRev( stringValue, pos, caseSensitive ); + if (pos == -1) + break; + if ((pos > 0 && where.at(pos-1).isLetterOrNumber()) + ||((pos+stringLength-1) < (whereLength-1) && where.at(pos+stringLength-1+1).isLetterOrNumber())) + { + // invalid match because before or after the string there is non-white space + pos--; + if (pos < 0) // it can make pos < 0 + break; + } + else + break; + }//while + firstCharacter = pos; + } + else {// !wholeWordsOnly + firstCharacter = where.findRev( stringValue, pos, caseSensitive ); + } + return firstCharacter != -1; + } + } + else if (matchWholeField) { + if (firstCharacter != -1 && firstCharacter != 0) { //we're not at 0-th char + firstCharacter = -1; + } + else if ( (caseSensitive ? where : where.lower()) == stringValue) { + firstCharacter = 0; + return true; + } + } + else {// matchStartOfField + if (firstCharacter != -1 && firstCharacter != 0) { //we're not at 0-th char + firstCharacter = -1; + } + else if (where.startsWith(stringValue, caseSensitive)) { + if (wholeWordsOnly) { + // If where.length() < stringValue.length(), true will be returned too - fine. + return !where.at( stringValue.length() ).isLetterOrNumber(); + } + firstCharacter = 0; + return true; + } + } + return false; +} + +tristate KexiDataAwareObjectInterface::find(const QVariant& valueToFind, + const KexiSearchAndReplaceViewInterface::Options& options, bool next) +{ + if (!hasData()) + return cancelled; + const QVariant prevSearchedValue( m_recentlySearchedValue ); + m_recentlySearchedValue = valueToFind; + const KexiSearchAndReplaceViewInterface::Options::SearchDirection prevSearchDirection = m_recentSearchDirection; + m_recentSearchDirection = options.searchDirection; + if (valueToFind.isNull() || valueToFind.toString().isEmpty()) + return cancelled; + + const bool forward = (options.searchDirection == KexiSearchAndReplaceViewInterface::Options::SearchUp) + ? !next : next; //direction can be reversed + + if ((!prevSearchedValue.isNull() && prevSearchedValue!=valueToFind) + || (prevSearchDirection!=options.searchDirection && options.searchDirection==KexiSearchAndReplaceViewInterface::Options::SearchAllRows)) + { + // restart searching when value has been changed or new direction is SearchAllRows + m_positionOfRecentlyFoundValue.exists = false; + } + + const bool startFrom1stRowAndCol = !m_positionOfRecentlyFoundValue.exists && next + && options.searchDirection == KexiSearchAndReplaceViewInterface::Options::SearchAllRows; + const bool startFromLastRowAndCol = + (!m_positionOfRecentlyFoundValue.exists && !next && options.searchDirection == KexiSearchAndReplaceViewInterface::Options::SearchAllRows) + ||(m_curRow >= rows() && !forward); //we're at "insert" row, and searching backwards: move to the last cell + + if (!startFrom1stRowAndCol && !startFromLastRowAndCol && m_curRow >= rows()) { + //we're at "insert" row, and searching forward: no chances to find something + return false; + } + KexiTableViewData::Iterator it( (startFrom1stRowAndCol || startFromLastRowAndCol) + ? m_data->iterator() : *m_itemIterator /*start from the current cell*/ ); + if (startFromLastRowAndCol) + it.toLast(); + int firstCharacter; + if (m_positionOfRecentlyFoundValue.exists) {// start after the next/prev char position + if (forward) + firstCharacter = m_positionOfRecentlyFoundValue.lastCharacter + 1; + else { + firstCharacter = (m_positionOfRecentlyFoundValue.firstCharacter > 0) ? + (m_positionOfRecentlyFoundValue.firstCharacter - 1) : INT_MAX /* this means 'before first'*/; + } + } + else { + firstCharacter = -1; //forward ? -1 : INT_MAX; + } + + const int columnsCount = m_data->columnsCount(); + int row, col; + if (startFrom1stRowAndCol) { + row = 0; + col = 0; + } + else if (startFromLastRowAndCol) { + row = rows()-1; + col = columnsCount-1; + } + else { + row = m_curRow; + col = m_curCol; + } + + //sache some flags for efficiency + const bool matchAnyPartOfField + = options.textMatching == KexiSearchAndReplaceViewInterface::Options::MatchAnyPartOfField; + const bool matchWholeField + = options.textMatching == KexiSearchAndReplaceViewInterface::Options::MatchWholeField; + const bool caseSensitive = options.caseSensitive; + const bool wholeWordsOnly = options.wholeWordsOnly; +//unused const bool promptOnReplace = options.promptOnReplace; + int columnNumber = (options.columnNumber == KexiSearchAndReplaceViewInterface::Options::CurrentColumn) + ? m_curCol : options.columnNumber; + if (columnNumber>=0) + col = columnNumber; + const bool lookInAllColumns = columnNumber == KexiSearchAndReplaceViewInterface::Options::AllColumns; + int firstColumn; // real number of the first column, can be smaller than lastColumn if forward==true + int lastColumn; // real number of the last column + if (lookInAllColumns) { + firstColumn = forward ? 0 : columnsCount-1; + lastColumn = forward ? columnsCount-1 : 0; + } + else { + firstColumn = columnNumber; + lastColumn = columnNumber; + } + const QString stringValue( caseSensitive ? valueToFind.toString() : valueToFind.toString().lower() ); + const int stringLength = stringValue.length(); + + // search + const int prevRow = m_curRow; + KexiTableItem *item; + while ( (item = it.current()) ) { + for (; forward ? col <= lastColumn : col >= lastColumn; + col = forward ? (col+1) : (col-1)) + { + const QVariant cell( item->at( m_indicesForVisibleValues[ col ] ) ); + if (findInString(stringValue, stringLength, cell.toString(), firstCharacter, + matchAnyPartOfField, matchWholeField, caseSensitive, wholeWordsOnly, forward)) + { + //*m_itemIterator = it; + //m_currentItem = *it; + //m_curRow = row; + //m_curCol = col; + setCursorPosition(row, col, true/*forceSet*/); + if (prevRow != m_curRow) + updateRow(prevRow); + // remember the exact position for the found value + m_positionOfRecentlyFoundValue.exists = true; + m_positionOfRecentlyFoundValue.firstCharacter = firstCharacter; +//! @todo for regexp lastCharacter should be computed + m_positionOfRecentlyFoundValue.lastCharacter = firstCharacter + stringLength - 1; + return true; + } + }//for + if (forward) { + ++it; + ++row; + } + else { + --it; + --row; + } + col = firstColumn; + }//while + return false; +} + +tristate KexiDataAwareObjectInterface::findNextAndReplace( + const QVariant& valueToFind, const QVariant& replacement, + const KexiSearchAndReplaceViewInterface::Options& options, bool replaceAll) +{ + Q_UNUSED(replacement); + Q_UNUSED(options); + Q_UNUSED(replaceAll); + + if (isReadOnly()) + return cancelled; + if (valueToFind.isNull() || valueToFind.toString().isEmpty()) + return cancelled; + //! @todo implement KexiDataAwareObjectInterface::findAndReplace() + return false; +} diff --git a/kexi/widget/tableview/kexidataawareobjectiface.h b/kexi/widget/tableview/kexidataawareobjectiface.h new file mode 100644 index 00000000..4cf2aa6a --- /dev/null +++ b/kexi/widget/tableview/kexidataawareobjectiface.h @@ -0,0 +1,918 @@ +/* This file is part of the KDE project + Copyright (C) 2005-2007 Jaroslaw Staniek <js@iidea.pl> + + Based on KexiTableView code. + Copyright (C) 2002 Till Busch <till@bux.at> + Copyright (C) 2003 Lucijan Busch <lucijan@gmx.at> + Copyright (C) 2003 Daniel Molkentin <molkentin@kde.org> + Copyright (C) 2003 Joseph Wenninger <jowenn@kde.org> + + 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. +*/ + +#ifndef KEXIDATAAWAREOBJECTINTERFACE_H +#define KEXIDATAAWAREOBJECTINTERFACE_H + +#include <qguardedptr.h> + +#include <qtimer.h> +#include <kdebug.h> +#include <widget/utils/kexiarrowtip.h> +#include <kexisearchandreplaceiface.h> +#include "kexitableviewdata.h" + +class QObject; +class QScrollBar; +class KPopupMenu; +class KexiTableItem; +class KexiTableViewData; +class KexiRecordMarker; +class KexiTableViewHeader; +class KexiRecordNavigator; +#include <kexidataiteminterface.h> + +namespace KexiDB { + class RowEditBuffer; +} + +//! default column width in pixels +#define KEXI_DEFAULT_DATA_COLUMN_WIDTH 120 + +//! \brief The KexiDataAwareObjectInterface is an interface for record-based data object. +/** This interface is implemented by KexiTableView and KexiFormView + and used by KexiDataAwareView. If yu're implementing this interface, + add KEXI_DATAAWAREOBJECTINTERFACE convenience macro just after Q_OBJECT. + + You should add following code to your destructor so data is deleted: + \code + if (m_owner) + delete m_data; + m_data = 0; + \endcode + This is not performed in KexiDataAwareObjectInterface because you may need + to access m_data in your desctructor. +*/ +class KEXIDATATABLE_EXPORT KexiDataAwareObjectInterface +{ + public: + KexiDataAwareObjectInterface(); + virtual ~KexiDataAwareObjectInterface(); + + /*! Sets data for this object. if \a owner is true, the object will own + \a data and therefore will be destroyed when needed, else: \a data is (possibly) shared and + not owned by the widget. + If widget already has _different_ data object assigned (and owns this data), + old data is destroyed before new assignment. + */ + void setData( KexiTableViewData *data, bool owner = true ); + + /*! \return data structure displayed for this object */ + inline KexiTableViewData *data() const { return m_data; } + + /*! \return currently selected column number or -1. */ + inline int currentColumn() const { return m_curCol; } + + /*! \return currently selected row number or -1. */ + inline int currentRow() const { return m_curRow; } + + /*! \return last row visible on the screen (counting from 0). + The returned value is guaranteed to be smaller or equal to currentRow() or -1 + if there are no rows. */ + virtual int lastVisibleRow() const = 0; + + /*! \return currently selected item (row data) or null. */ + KexiTableItem *selectedItem() const { return m_currentItem; } + + /*! \return number of rows in this view. */ + int rows() const; + + /*! \return number of visible columns in this view. + By default returns dataColumns(), what is proper table view. + In case of form view, there can be a number of duplicated columns defined + (data-aware widgets, see KexiFormScrollView::columns()), + so columns() can return greater number than dataColumns(). */ + virtual int columns() const { return dataColumns(); } + + /*! Helper function. + \return number of columns of data. */ + int dataColumns() const; + + /*! \return true if data represented by this object + is not editable (it can be editable with other ways although, + outside of this object). */ + virtual bool isReadOnly() const; + + /*! Sets readOnly flag for this object. + Unless the flag is set, the widget inherits readOnly flag from it's data + structure assigned with setData(). The default value if false. + + This method is useful when you need to switch on the flag indepentently + from the data structure. + Note: it is not allowed to force readOnly off + when internal data is readOnly - in that case the method does nothing. + You can check internal data flag calling data()->isReadOnly(). + + If \a set is true, insertingEnabled flag will be cleared automatically. + \sa isInsertingEnabled() + */ + void setReadOnly(bool set); + + /*! \return true if sorting is enabled. */ + inline bool isSortingEnabled() const { return m_isSortingEnabled; } + + /*! Sets sorting on column \a col, or (when \a col == -1) sets rows unsorted + this will do not work if sorting is disabled with setSortingEnabled() */ + virtual void setSorting(int col, bool ascending=true); + + /*! Enables or disables sorting for this object + This method is different that setSorting() because it prevents both user + and programmer from sorting by clicking a column's header or calling setSorting(). + By default sorting is enabled. + */ + virtual void setSortingEnabled(bool set); + + /*! \return sorted column number or -1 if no column is sorted within data. + This does not mean that any sorting has been performed within GUI of this object, + because the data could be changed in the meantime outside of this GUI object. */ + int dataSortedColumn() const; + + /*! \return 1 if ascending order for data sorting data is set, -1 for descending, + 0 for no sorting. + This does not mean that any sorting has been performed within GUI of this objetct, + because the data could be changed in the meantime outside of this GUI object. + */ + int dataSortingOrder() const; + + /*! Sorts all rows by column selected with setSorting(). + If there is currently row edited, it is accepted. + If acception failed, sort() will return false. + \return true on success. */ + virtual bool sort(); + + /*! Sorts currently selected column in ascending order. + This slot is used typically for "data_sort_az" action. */ + void sortAscending(); + + /*! Sorts currently selected column in descending order. + This slot is used typically for "data_sort_za" action. */ + void sortDescending(); + + /*! \return true if data inserting is enabled (the default). */ + virtual bool isInsertingEnabled() const; + + /*! Sets insertingEnabled flag. If true, empty row is available + at the end of this widget for new entering new data. + Unless the flag is set, the widget inherits insertingEnabled flag from it's data + structure assigned with setData(). The default value if false. + + Note: it is not allowed to force insertingEnabled on when internal data + has insertingEnabled set off - in that case the method does nothing. + You can check internal data flag calling data()->insertingEnabled(). + + Setting this flag to true will have no effect if read-only flag is true. + \sa setReadOnly() + */ + void setInsertingEnabled(bool set); + + /*! \return true if row deleting is enabled. + Equal to deletionPolicy() != NoDelete && !isReadOnly()). */ + bool isDeleteEnabled() const; + + /*! \return true if inserting empty rows are enabled (false by default). + Mostly usable for not db-aware objects (e.g. used in Kexi Alter Table). + Note, that if inserting is disabled, or the data set is read-only, + this flag will be ignored. */ + bool isEmptyRowInsertingEnabled() const { return m_emptyRowInsertingEnabled; } + + /*! Sets emptyRowInserting flag. + Note, that if inserting is disabled, this flag is ignored. */ + void setEmptyRowInsertingEnabled(bool set); + + /*! Enables or disables filtering. Filtering is enabled by default. */ + virtual void setFilteringEnabled(bool set); + + /*! \return true if filtering is enabled. */ + inline bool isFilteringEnabled() const { return m_isFilteringEnabled; } + + /*! Added for convenience: configure this object + to behave more like spreadsheet (it's used for things like alter-table view). + - hides navigator + - disables sorting, inserting and filtering + - enables accepting row after cell accepting; see setAcceptsRowEditAfterCellAccepting() + - enables inserting empty row; see setEmptyRowInsertingEnabled() */ + virtual void setSpreadSheetMode(); + + /*! \return true id "spreadSheetMode" is enabled. It's false by default. */ + bool spreadSheetMode() const { return m_spreadSheetMode; } + + /*! \return true if currently selected row is edited. */ + inline bool rowEditing() const { return m_rowEditing; } + + enum DeletionPolicy + { + NoDelete = 0, + AskDelete = 1, + ImmediateDelete = 2, + SignalDelete = 3 + }; + + /*! \return deletion policy for this object. + The default (after allocating) is AskDelete. */ + DeletionPolicy deletionPolicy() const { return m_deletionPolicy; } + + virtual void setDeletionPolicy(DeletionPolicy policy); + + /*! Deletes currently selected record; does nothing if no record + is currently selected. If record is in edit mode, editing + is cancelled before deleting. */ + virtual void deleteCurrentRow(); + + /*! Inserts one empty row above row \a row. If \a row is -1 (the default), + new row is inserted above the current row (or above 1st row if there is no current). + A new item becomes current if row is -1 or if row is equal currentRow(). + This method does nothing if: + -inserting flag is disabled (see isInsertingEnabled()) + -read-only flag is set (see isReadOnly()) + \ return inserted row's data + */ + virtual KexiTableItem *insertEmptyRow(int row = -1); + + /*! For reimplementation: called by deleteItem(). If returns false, deleting is aborted. + Default implementation just returns true. */ + virtual bool beforeDeleteItem(KexiTableItem *item); + + /*! Deletes \a item. Used by deleteCurrentRow(). Calls beforeDeleteItem() before deleting, + to double-check if deleting is allowed. + \return true on success. */ + bool deleteItem(KexiTableItem *item);//, bool moveCursor=true); + + /*! Inserts newItem at \a row. -1 means current row. Used by insertEmptyRow(). */ + void insertItem(KexiTableItem *newItem, int row = -1); + + /*! Clears entire table data, its visible representation + and deletes data at database backend (if this is db-aware object). + Does not clear columns information. + Does not destroy KexiTableViewData object (if present) but only clears its contents. + Displays confirmation dialog if \a ask is true (the default is false). + Repaints widget if \a repaint is true (the default). + For empty tables, true is returned immediately. + If isDeleteEnabled() is false, false is returned. + For spreadsheet mode all current rows are just replaced by empty rows. + \return true on success, false on failure, and cancelled if user cancelled deletion + (only possible if \a ask is true). + */ + tristate deleteAllRows(bool ask = false, bool repaint = true); + + /*! \return maximum number of rows that can be displayed per one "page" + for current view's size. */ + virtual int rowsPerPage() const = 0; + + virtual void selectRow(int row); + virtual void selectNextRow(); + virtual void selectPrevRow(); + virtual void selectNextPage(); //!< page down action + virtual void selectPrevPage(); //!< page up action + virtual void selectFirstRow(); + virtual void selectLastRow(); + virtual void addNewRecordRequested(); + + /*! Clears current selection. Current row and column will be now unspecified: + currentRow(), currentColumn() will return -1, and selectedItem() will return null. */ + virtual void clearSelection(); + + /*! Moves cursor to \a row and \a col. If \a col is -1, current column number is used. + If forceSet is true, cursor position is updated even if \a row and \a col doesn't + differ from actual position. */ + virtual void setCursorPosition(int row, int col = -1, bool forceSet = false); + + /*! Ensures that cell at \a row and \a col is visible. + If \a col is -1, current column number is used. \a row and \a col (if not -1) must + be between 0 and rows() (or cols() accordingly). */ + virtual void ensureCellVisible(int row, int col/*=-1*/) = 0; + + /*! Specifies, if this object automatically accepts + row editing (using acceptRowEdit()) on accepting any cell's edit + (i.e. after acceptEditor()). \sa acceptsRowEditAfterCellAccepting() */ + virtual void setAcceptsRowEditAfterCellAccepting(bool set); + + /*! \return true, if this object automatically accepts + row editing (using acceptRowEdit()) on accepting any cell's edit + (i.e. after acceptEditor()). + By default this flag is set to false. + Not that if the query for this table has given constraints defined, + like NOT NULL / NOT EMPTY for more than one field - editing a record would + be impossible for the flag set to true, because of constraints violation. + However, setting this flag to true can be useful especially for not-db-aware + data set (it's used e.g. in Kexi Alter Table's field editor). */ + bool acceptsRowEditAfterCellAccepting() const { return m_acceptsRowEditAfterCellAccepting; } + + /*! \return true, if this table accepts dropping data on the rows. */ + bool dropsAtRowEnabled() const { return m_dropsAtRowEnabled; } + + /*! Specifies, if this table accepts dropping data on the rows. + If enabled: + - dragging over row is indicated by drawing a line at bottom side of this row + - dragOverRow() signal will be emitted on dragging, + -droppedAtRow() will be emitted on dropping + By default this flag is set to false. */ + virtual void setDropsAtRowEnabled(bool set); + + /*! \return currently used data (field/cell) editor or 0 if there is no data editing. */ + inline KexiDataItemInterface *editor() const { return m_editor; } + + /*! Cancels row editing All changes made to the editing + row during this current session will be undone. + \return true on success or false on failure (e.g. when editor does not exist) */ + virtual bool cancelRowEdit(); + + /*! Accepts row editing. All changes made to the editing + row during this current session will be accepted (saved). + \return true if accepting was successful, false otherwise + (e.g. when current row contain data that does not meet given constraints). */ + virtual bool acceptRowEdit(); + + virtual void removeEditor(); + + /*! Cancels changes made to the currently active editor. + Reverts the editor's value to old one. + \return true on success or false on failure (e.g. when editor does not exist) */ + virtual bool cancelEditor(); + + //! Accepst changes made to the currently active editor. + //! \return true on success or false on failure (e.g. when editor does not exist or there is data validation error) + virtual bool acceptEditor(); + + //! Creates editors and shows it, what usually means the beginning of a cell editing + virtual void createEditor(int row, int col, const QString& addText = QString::null, + bool removeOld = false) = 0; + + /*! Used when Return key is pressed on cell, the cell has been double clicked + or "+" navigator's button is clicked. + Also used when we want to continue editing a cell after "invalid value" message + was displayed (in this case, \a setText is usually not empty, what means + that text will be set in the cell replacing previous value). + */ + virtual void startEditCurrentCell(const QString& setText = QString::null); + + /*! Deletes currently selected cell's contents, if allowed. + In most cases delete is not accepted immediately but "row editing" mode is just started. */ + virtual void deleteAndStartEditCurrentCell(); + + inline KexiTableItem *itemAt(int row) const; + + /*! \return column information for column number \a col. + Default implementation just returns column # col, + but for Kexi Forms column data + corresponding to widget number is used here + (see KexiFormScrollView::fieldNumberForColumn()). */ + virtual KexiTableViewColumn* column(int col); + + /*! \return field number within data model connected to a data-aware + widget at column \a col. Can return -1 if there's no such column. */ + virtual int fieldNumberForColumn(int col) { return col; } + + bool hasDefaultValueAt(const KexiTableViewColumn& tvcol); + + const QVariant* bufferedValueAt(int col, bool useDefaultValueIfPossible = true); + + //! \return a type of column \a col - one of KexiDB::Field::Type + int columnType(int col); + + //! \return default value for column \a col + QVariant columnDefaultValue(int col) const; + + /*! \return true is column \a col is editable. + Default implementation takes information about 'readOnly' flag from data member. + Within forms, this is reimplemented for checking 'readOnly' flag from a widget + ('readOnly' flag from data member is still checked though). + */ + virtual bool columnEditable(int col); + + /*! Redraws the current cell. To be implemented. */ + virtual void updateCurrentCell() = 0; + + inline KexiRecordMarker* verticalHeader() const { return m_verticalHeader; } + + //! signals + virtual void itemChanged(KexiTableItem *, int row, int col) = 0; + virtual void itemChanged(KexiTableItem *, int row, int col, QVariant oldValue) = 0; + virtual void itemDeleteRequest(KexiTableItem *, int row, int col) = 0; + virtual void currentItemDeleteRequest() = 0; + //! Emitted for spreadsheet mode when an item was deleted and a new item has been appended + virtual void newItemAppendedForAfterDeletingInSpreadSheetMode() = 0; + + /*! Data has been refreshed on-screen - emitted from initDataContents(). */ + virtual void dataRefreshed() = 0; + virtual void dataSet( KexiTableViewData *data ) = 0; + + /*! \return a pointer to context menu. This can be used to plug some actions there. */ + KPopupMenu* contextMenu() const { return m_popupMenu; } + + /*! \return true if the context menu is enabled (visible) for the view. + True by default. */ + bool contextMenuEnabled() const { return m_contextMenuEnabled; } + + /*! Enables or disables the context menu for the view. */ + void setContextMenuEnabled(bool set) { m_contextMenuEnabled = set; } + + /*! \return true if vertical scrollbar's tooltips are enabled (true by default). */ + bool scrollbarToolTipsEnabled() const; + + /*! Enables or disables vertical scrollbar's tooltip. */ + void setScrollbarToolTipsEnabled(bool set); + + /*! Typically handles pressing Enter or F2 key: + if current cell has boolean type, toggles it's value, + otherwise starts editing (startEditCurrentCell()). */ + void startEditOrToggleValue(); + + /*! \return true if new row is edited; implies: rowEditing==true. */ + inline bool newRowEditing() const { return m_newRowEditing; } + + /*! Reaction on toggling a boolean value of a cell: + we're starting to edit the cell and inverting it's state. */ + virtual void boolToggled(); + + virtual void connectCellSelectedSignal(const QObject* receiver, + const char* intIntMember) = 0; + + virtual void connectRowEditStartedSignal(const QObject* receiver, + const char* intMember) = 0; + + virtual void connectRowEditTerminatedSignal(const QObject* receiver, + const char* voidMember) = 0; + + virtual void connectReloadActionsSignal(const QObject* receiver, + const char* voidMember) = 0; + + virtual void connectDataSetSignal(const QObject* receiver, + const char* kexiTableViewDataMember) = 0; + + virtual void connectToReloadDataSlot(const QObject* sender, + const char* voidSignal) = 0; + + virtual void slotDataDestroying(); + + //! Copy current selection to a clipboard (e.g. cell) + virtual void copySelection() = 0; + + //! Cut current selection to a clipboard (e.g. cell) + virtual void cutSelection() = 0; + + //! Paste current clipboard contents (e.g. to a cell) + virtual void paste() = 0; + + /*! Finds \a valueToFind within the data items + \a options are used to control the process. Selection is moved to found value. + If \a next is true, "find next" is performed, else "find previous" is performed. + + Searching behaviour also depends on status of the previous search: for every search, + position of the cells containing the found value is stored internally + by the data-aware interface (not in options). + Moreover, position (start, end) of the found value is also stored. + Thus, the subsequent search will reuse this information to be able to start + searching exactly after the previously found value (or before for "find previous" option). + The flags can be zeroed, what will lead to seaching from the first character + of the current item (cell). + + \return true if value has been found, false if value has not been found, + and cancelled if there is nothing to find or there is no data to search in. */ + virtual tristate find(const QVariant& valueToFind, + const KexiSearchAndReplaceViewInterface::Options& options, bool next); + + /*! Finds \a valueToFind within the data items and replaces with \a replacement + \a options are used to control the process. + \return true if value has been found and replaced, false if value + has not been found and replaced, and cancelled if there is nothing + to find or there is no data to search in or the data is read only. + If \a replaceAll is true, all found values are replaced. */ + virtual tristate findNextAndReplace(const QVariant& valueToFind, + const QVariant& replacement, + const KexiSearchAndReplaceViewInterface::Options& options, bool replaceAll); + + /*! \return vertical scrollbar */ + virtual QScrollBar* verticalScrollBar() const = 0; + + /*! Used in KexiTableView::keyPressEvent() (and in continuous forms). + \return true when the key press event \e was consumed. + You should also check e->isAccepted(), if it's true, nothing should be done; + if it is false, you should call setCursorPosition() for the altered \a curCol + and \c curRow variables. + + If \a moveToFirstField is not 0, *moveToFirstField will be set to true + when the cursor should be moved to the first field (in tab order) and to false otherwise. + If \a moveToLastField is not 0, *moveToLastField will be set to true + when the cursor should be moved to the last field (in tab order) and to false otherwise. + Note for forms: if moveToFirstField and moveToLastField are not 0, + \a curCol is altered after calling this method, so setCursorPosition() will set to + the index of an appropriate column (field). This is needed because field widgets can be + inserted and ordered in custom tab order, so the very first field in the data source + can be other than the very first field in the form. + + Used by KexiTableView::keyPressEvent() and KexiTableView::keyPressEvent(). */ + virtual bool handleKeyPress(QKeyEvent *e, int &curRow, int &curCol, bool fullRowSelection, + bool *moveToFirstField = 0, bool *moveToLastField = 0); + + protected: + /*! Reimplementation for KexiDataAwareObjectInterface. + Initializes data contents (resizes it, sets cursor at 1st row). + Sets record count for record navigator. + Sets cursor positin (using setCursorPosition()) to first row or sets + (-1, -1) position if no rows are available. + Called on setData(). Also called once on show event after + refreshRequested() signal was received from KexiTableViewData object. */ + virtual void initDataContents(); + + /*! Clears columns information and thus all internal table data + and its visible representation. Repaints widget if \a repaint is true. */ + virtual void clearColumns(bool repaint = true); + + /*! Called by clearColumns() to clear internals of the object. + For example, KexiTableView removes contents of it's horizontal header. */ + virtual void clearColumnsInternal(bool repaint) = 0; + + /*! @internal for implementation + This should append another section within horizontal header or any sort of caption + for a field using provided names. \a width is a hint for new field's width. */ + virtual void addHeaderColumn(const QString& caption, const QString& description, + const QIconSet& icon, int size) = 0; + + /*! @internal for implementation + \return sorting order (within GUI): -1: descending, 1: ascending, 0: no sorting. + This does not mean that any sorting has been performed within GUI of this object, + because the data could be changed in the meantime outside of this GUI object. + @see dataSortingOrder()*/ + virtual int currentLocalSortingOrder() const = 0; + + /*! @internal for implementation + \return sorted column number for this widget or -1 if no column + is sorted witin GUI. + This does not mean that the same sorting is performed within data member + which is used by this widget, because the data could be changed in the meantime + outside of this GUI widget. + @see dataSortedColumn() */ + virtual int currentLocalSortColumn() const = 0; + + /*! @internal for implementation + Shows sorting indicator order within GUI: -1: descending, 1: ascending, + 0: no sorting. This should not perform any sorting within data member + which is used by this object. + col = -1 should mean "no sorting" as well. */ + virtual void setLocalSortingOrder(int col, int order) = 0; + + /*! @internal Sets order for \a column: -1: descending, 1: ascending, + 0: invert order */ + virtual void sortColumnInternal(int col, int order = 0); + + /*! @internal for implementation + Updates GUI after sorting. + After sorting you need to ensure current row and column + is visible to avoid user confusion. For exaple, in KexiTableView + implementation, current cell is centered (if possible) + and updateContents() is called. */ + virtual void updateGUIAfterSorting() = 0; + + /*! Emitted in initActions() to force reload actions + You should remove existing actions and add them again. + Define and emit reloadActions() signal here. */ + virtual void reloadActions() = 0; + + /*! Reloads data for this object. */ + virtual void reloadData(); + + /*! for implementation as a signal */ + virtual void itemSelected(KexiTableItem *) = 0; + + /*! for implementation as a signal */ + virtual void cellSelected(int col, int row) = 0; + + /*! for implementation as a signal */ + virtual void sortedColumnChanged(int col) = 0; + + /*! for implementation as a signal */ + virtual void rowEditTerminated(int row) = 0; + + /*! Clear temporary members like the pointer to current editor. + If you reimplement this method, don't forget to call this one. */ + virtual void clearVariables(); + + /*! @internal + Creates editor structure without filling it with data. + Used in createEditor() and few places to be able to display cell contents + dependending on its type. If \a ignoreMissingEditor is false (the default), + and editor cannot be instantiated, current row editing (if present) is cancelled. + */ + virtual KexiDataItemInterface *editor( int col, bool ignoreMissingEditor = false ) = 0; + + /*! Updates editor's position, size and shows its focus (not the editor!) + for \a row and \a col, using editor(). Does nothing if editor not found. */ + virtual void editorShowFocus( int row, int col ) = 0; + + /*! Redraws specified cell. */ + virtual void updateCell(int row, int col) = 0; + + /*! Redraws all cells of specified row. */ + virtual void updateRow(int row) = 0; + + /*! Updates contents of the widget. Just call update() here on your widget. */ + virtual void updateWidgetContents() = 0; + + /*! Updates widget's contents size e.g. using QScrollView::resizeContents(). */ + virtual void updateWidgetContentsSize() = 0; + + /*! Updates scrollbars of the widget. + QScrollView::updateScrollbars() will be usually called here. */ + virtual void updateWidgetScrollBars() = 0; + + /*! @internal + Updates row appearance after canceling row edit. + Used by cancelRowEdit(). By default just calls updateRow(m_curRow). + Reimplemented by KexiFormScrollView. */ + virtual void updateAfterCancelRowEdit(); + + /*! @internal + Updates row appearance after accepting row edit. + Used by acceptRowEdit(). By default just calls updateRow(m_curRow). + Reimplemented by KexiFormScrollView. */ + virtual void updateAfterAcceptRowEdit(); + + //! Handles KexiTableViewData::rowRepaintRequested() signal + virtual void slotRowRepaintRequested(KexiTableItem& item) { Q_UNUSED( item ); } + + //! Handles KexiTableViewData::aboutToDeleteRow() signal. Prepares info for slotRowDeleted(). + virtual void slotAboutToDeleteRow(KexiTableItem& item, KexiDB::ResultInfo* result, + bool repaint); + + //! Handles KexiTableViewData::rowDeleted() signal to repaint when needed. + virtual void slotRowDeleted(); + + //! Handles KexiTableViewData::rowInserted() signal to repaint when needed. + virtual void slotRowInserted(KexiTableItem *item, bool repaint); + + //! Like above, not db-aware version + virtual void slotRowInserted(KexiTableItem *item, uint row, bool repaint); + + virtual void slotRowsDeleted( const QValueList<int> & ) {} + + //! for sanity checks (return true if m_data is present; else: outputs warning) + inline bool hasData() const; + + /*! Only needed for forms: called by KexiDataAwareObjectInterface::setCursorPosition() + if cursor's position is really changed. */ + virtual void selectCellInternal() {} + + /*! Used in KexiDataAwareObjectInterface::slotRowDeleted() + to repaint tow \a row and all visible below. + Implemented if there is more than one row displayed, i.e. currently for KexiTableView. */ + virtual void updateAllVisibleRowsBelow(int row) { Q_UNUSED( row ); } + + //! Call this from the subclass. */ + virtual void focusOutEvent(QFocusEvent* e); + + /*! Handles verticalScrollBar()'s valueChanged(int) signal. + Called when vscrollbar's value has been changed. + Call this method from the subclass. */ + virtual void vScrollBarValueChanged(int v); + + /*! Handles sliderReleased() signal of the verticalScrollBar(). Used to hide the "row number" tooltip. */ + virtual void vScrollBarSliderReleased(); + + /*! Handles timeout() signal of the m_scrollBarTipTimer. If the tooltip is visible, + m_scrollBarTipTimerCnt is set to 0 and m_scrollBarTipTimerCnt is restarted; + else the m_scrollBarTipTimerCnt is just set to 0.*/ + virtual void scrollBarTipTimeout(); + + /*! Shows error message box suitable for \a resultInfo. This can be "sorry" or "detailedSorry" + message box or "queryYesNo" if resultInfo->allowToDiscardChanges is true. + \return code of button clicked: KMessageBox::Ok in case of "sorry" or "detailedSorry" messages + and KMessageBox::Yes or KMessageBox::No in case of "queryYesNo" message. */ + int showErrorMessageForResult(KexiDB::ResultInfo* resultInfo); + + /*! Prepares array of indices of visible values to search within. + This is per-interface global cache. + Needed for faster lookup because there could be lookup values. + Called whenever columns definition changes, i.e. in setData() and clearColumns(). + @see find() */ + void updateIndicesForVisibleValues(); + + //! data structure displayed for this object + KexiTableViewData *m_data; + + //! true if m_data member is owned by this object + bool m_owner : 1; + + //! cursor position + int m_curRow, m_curCol; + + //! current data item + KexiTableItem *m_currentItem; + + //! data item's iterator + KexiTableViewData::Iterator *m_itemIterator; + + //! item data for inserting + KexiTableItem *m_insertItem; + + //! when (current or new) row is edited - changed field values are temporary stored here +// KexiDB::RowEditBuffer *m_rowEditBuffer; + + /*! true if currently selected row is edited */ + bool m_rowEditing : 1; + + /*! true if new row is edited; implies: rowEditing==true. */ + bool m_newRowEditing : 1; + + /*! 'sorting by column' availability flag for widget */ + bool m_isSortingEnabled : 1; + + /*! true if filtering is enabled for the view. */ + bool m_isFilteringEnabled : 1; + + /*! Public version of 'acceptsRowEditAfterCellAcceptin' flag (available for a user). + It's OR'es together with above flag. + */ + bool m_acceptsRowEditAfterCellAccepting : 1; + + /*! Used in acceptEditor() to avoid infinite recursion, + eg. when we're calling acceptRowEdit() during cell accepting phase. */ + bool m_inside_acceptEditor : 1; + + /*! @internal if true, this object automatically accepts + row editing (using acceptRowEdit()) on accepting any cell's edit + (i.e. after acceptEditor()). */ + bool m_internal_acceptsRowEditAfterCellAccepting : 1; + + /*! true, if inserting empty rows are enabled (false by default) */ + bool m_emptyRowInsertingEnabled : 1; + + /*! Contains 1 if the object is readOnly, 0 if not; + otherwise (-1 means "do not know") the 'readOnly' flag from object's + internal data structure (KexiTableViewData *KexiTableView::m_data) is reused. + */ + int m_readOnly; + +//! @todo really keep this here and not in KexiTableView? + /*! true if currently double click action was is performed + (so accept/cancel editor shoudn't be executed) */ + bool m_contentsMousePressEvent_dblClick : 1; + + /*! like for readOnly: 1 if inserting is enabled */ + int m_insertingEnabled; + + /*! true, if initDataContents() should be called on show event. */ + bool m_initDataContentsOnShow : 1; + + /*! Set to true in setCursorPosition() to indicate that cursor position was set + before show() and it shouldn't be changed on show(). + Only used if initDataContentsOnShow is true. */ + bool m_cursorPositionSetExplicityBeforeShow : 1; + + /*! true if spreadSheetMode is enabled. False by default. + @see KexiTableView::setSpreadSheetMode() */ + bool m_spreadSheetMode : 1; + + /*! true, if this table accepts dropping data on the rows (false by default). */ + bool m_dropsAtRowEnabled : 1; + + /*! true, if this entire (visible) row should be updated when boving to other row. + False by default. For table view with 'row highlighting' flag enabled, it is true. */ + bool m_updateEntireRowWhenMovingToOtherRow : 1; + + DeletionPolicy m_deletionPolicy; + +//! @todo make generic interface out of KexiRecordMarker + KexiRecordMarker *m_verticalHeader; + +//! @todo make generic interface out of KexiTableViewHeader + KexiTableViewHeader *m_horizontalHeader; + + KexiDataItemInterface *m_editor; +// KexiTableEdit *m_editor; + + /*! Navigation panel, used if navigationPanelEnabled is true. */ + KexiRecordNavigator *m_navPanel; //!< main navigation widget + + bool m_navPanelEnabled : 1; + + /*! true, if certical header shouldn't be increased in + KexiTableView::slotRowInserted() because it was already done + in KexiTableView::createEditor(). */ + bool m_verticalHeaderAlreadyAdded : 1; + + /*! Row number that over which user drags a mouse pointer. + Used to indicate dropping possibility for that row. + Equal -1 if no indication is needed. */ + int m_dragIndicatorLine; + + /*! Context menu widget. */ + KPopupMenu *m_popupMenu; + + bool m_contextMenuEnabled : 1; + + //! Used by updateAfterCancelRowEdit() + bool m_alsoUpdateNextRow : 1; + + /*! Row number (>=0 or -1 == no row) that will be deleted in deleteRow(). + It is set in slotAboutToDeleteRow(KexiTableItem&,KexiDB::ResultInfo*,bool)) slot + received from KexiTableViewData member. + This value will be used in slotRowDeleted() after rowDeleted() signal + is received from KexiTableViewData member and then cleared (set to -1). */ + int m_rowWillBeDeleted; + + /*! Displays passive error popup label used when invalid data has been entered. */ + QGuardedPtr<KexiArrowTip> m_errorMessagePopup; + + /*! Used to enable/disable execution of vScrollBarValueChanged() + when users navigate through rows using keyboard, so vscrollbar tooltips are not visible. */ + bool m_vScrollBarValueChanged_enabled : 1; + + /*! True, if vscrollbar tooltips are enabled (true by default). */ + bool m_scrollbarToolTipsEnabled : 1; + + QLabel* m_scrollBarTip; //!< scrollbar tooltip + QTimer m_scrollBarTipTimer; //!< scrollbar tooltip's timer + uint m_scrollBarTipTimerCnt; //!< helper for timeout counting (scrollbar tooltip) + + //! Used to mark recently found value + class PositionOfValue { + public: + PositionOfValue() : firstCharacter(0), lastCharacter(0), exists(false) + {} + uint firstCharacter; + uint lastCharacter; + bool exists : 1; + }; + + /*! Used to mark recently found value. Updated on succesful execution of find(). + If the current cursor's position changes, or data in the current cell changes, + positionOfRecentlyFoundValue.exists is set to false. */ + PositionOfValue m_positionOfRecentlyFoundValue; + + /*! Used to compare whether we're looking for new value. */ + QVariant m_recentlySearchedValue; + + /*! Used to compare whether the search direction has changed. */ + KexiSearchAndReplaceViewInterface::Options::SearchDirection m_recentSearchDirection; + + //! Setup by updateIndicesForVisibleValues() and used by find() + QValueVector<uint> m_indicesForVisibleValues; +}; + +inline bool KexiDataAwareObjectInterface::hasData() const +{ + if (!m_data) + kdDebug() << "KexiDataAwareObjectInterface: No data assigned!" << endl; + return m_data!=0; +} + +inline KexiTableItem *KexiDataAwareObjectInterface::itemAt(int row) const +{ + KexiTableItem *item = m_data->at(row); + if (!item) + kdDebug() << "KexiTableView::itemAt(" << row << "): NO ITEM!!" << endl; + else { +/* kdDebug() << "KexiTableView::itemAt(" << row << "):" << endl; + int i=1; + for (KexiTableItem::Iterator it = item->begin();it!=item->end();++it,i++) + kdDebug() << i<<": " << (*it).toString()<< endl;*/ + } + return item; +} + +//! Convenience macro used for KexiDataAwareObjectInterface implementations. +#define KEXI_DATAAWAREOBJECTINTERFACE \ +public: \ + void connectCellSelectedSignal(const QObject* receiver, const char* intIntMember) { \ + connect(this, SIGNAL(cellSelected(int,int)), receiver, intIntMember); \ + } \ + void connectRowEditStartedSignal(const QObject* receiver, const char* intMember) { \ + connect(this, SIGNAL(rowEditStarted(int)), receiver, intMember); \ + } \ + void connectRowEditTerminatedSignal(const QObject* receiver, const char* voidMember) { \ + connect(this, SIGNAL(rowEditTerminated(int)), receiver, voidMember); \ + } \ + void connectReloadActionsSignal(const QObject* receiver, const char* voidMember) { \ + connect(this, SIGNAL(reloadActions()), receiver, voidMember); \ + } \ + void connectDataSetSignal(const QObject* receiver, \ + const char* kexiTableViewDataMember) { \ + connect(this, SIGNAL(dataSet(KexiTableViewData*)), receiver, kexiTableViewDataMember); \ + } \ + void connectToReloadDataSlot(const QObject* sender, const char* voidSignal) { \ + connect(sender, voidSignal, this, SLOT(reloadData())); \ + } + +#endif diff --git a/kexi/widget/tableview/kexidataawarepropertyset.cpp b/kexi/widget/tableview/kexidataawarepropertyset.cpp new file mode 100644 index 00000000..92fda11e --- /dev/null +++ b/kexi/widget/tableview/kexidataawarepropertyset.cpp @@ -0,0 +1,260 @@ +/* This file is part of the KDE project + Copyright (C) 2004-2005 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 "kexidataawarepropertyset.h" +#include "kexitableviewdata.h" +#include "kexidataawareobjectiface.h" + +#include <koproperty/property.h> +#include <kexiviewbase.h> + +#define MAX_FIELDS 101 //nice prime number (default prop. set vector size) + +KexiDataAwarePropertySet::KexiDataAwarePropertySet(KexiViewBase *view, + KexiDataAwareObjectInterface* dataObject) + : QObject( view, QCString(view->name())+"KexiDataAwarePropertySet" ) + , m_view(view) + , m_dataObject(dataObject) + , m_row(-99) +{ + m_sets.setAutoDelete(true); + +// connect(m_dataObject, SIGNAL(dataSet(KexiTableViewData*)), +// this, SLOT(slotDataSet(KexiTableViewData*))); + m_dataObject->connectDataSetSignal(this, SLOT(slotDataSet(KexiTableViewData*))); +// connect(m_dataObject, SIGNAL(cellSelected(int,int)), +// this, SLOT(slotCellSelected(int,int))); + m_dataObject->connectCellSelectedSignal(this, SLOT(slotCellSelected(int,int))); +// + slotDataSet( m_dataObject->data() ); + const bool wasDirty = view->dirty(); + clear(); + if (!wasDirty) + view->setDirty(false); +} + +KexiDataAwarePropertySet::~KexiDataAwarePropertySet() +{ +} + +void KexiDataAwarePropertySet::slotDataSet( KexiTableViewData *data ) +{ + if (!m_currentTVData.isNull()) { + m_currentTVData->disconnect( this ); + clear(); + } + m_currentTVData = data; + if (!m_currentTVData.isNull()) { + connect(m_currentTVData, SIGNAL(rowDeleted()), this, SLOT(slotRowDeleted())); + connect(m_currentTVData, SIGNAL(rowsDeleted( const QValueList<int> & )), + this, SLOT(slotRowsDeleted( const QValueList<int> & ))); + connect(m_currentTVData, SIGNAL(rowInserted(KexiTableItem*,uint,bool)), + this, SLOT(slotRowInserted(KexiTableItem*,uint,bool))); + connect(m_currentTVData, SIGNAL(reloadRequested()), + this, SLOT(slotReloadRequested())); + } +} + +void KexiDataAwarePropertySet::removeCurrentPropertySet() +{ + remove( m_dataObject->currentRow() ); +} + +void KexiDataAwarePropertySet::remove(uint row) +{ + KoProperty::Set *set = m_sets.at(row); + if (!set) + return; + set->debug(); + m_sets.remove(row); + m_view->setDirty(); + m_view->propertySetSwitched(); +} + +uint KexiDataAwarePropertySet::size() const +{ + return m_sets.size(); +} + +void KexiDataAwarePropertySet::clear(uint minimumSize) +{ + m_sets.clear(); + m_sets.resize(QMAX(minimumSize, MAX_FIELDS)); + m_view->setDirty(true); + m_view->propertySetSwitched(); +} + +void KexiDataAwarePropertySet::slotReloadRequested() +{ + clear(); +} + +void KexiDataAwarePropertySet::insert(uint row, KoProperty::Set* set, bool newOne) +{ + if (!set || row >= m_sets.size()) { + kexiwarn << "KexiDataAwarePropertySet::insert() invalid args: rew="<< row<< " propertyset="<< set<< endl; + return; + } + if (set->parent() && set->parent()!=this) { + kexiwarn << "KexiDataAwarePropertySet::insert() propertyset's parent must be NULL or this KexiDataAwarePropertySet" << endl; + return; + } + + m_sets.insert(row, set); + + connect(set, SIGNAL(propertyChanged(KoProperty::Set&, KoProperty::Property&)), m_view, SLOT(setDirty())); + + if (newOne) { + //add a special property indicating that this is brand new set, + //not just changed + KoProperty::Property* prop = new KoProperty::Property("newrow"); + prop->setVisible(false); + set->addProperty( prop ); + m_view->setDirty(); + } +} + +KoProperty::Set* KexiDataAwarePropertySet::currentPropertySet() const +{ + return (m_dataObject->currentRow() >= 0) ? m_sets.at( m_dataObject->currentRow() ) : 0; +} + +uint KexiDataAwarePropertySet::currentRow() const +{ + return m_dataObject->currentRow(); +} + +void KexiDataAwarePropertySet::slotRowDeleted() +{ + m_view->setDirty(); + removeCurrentPropertySet(); + + //let's move up all property sets that are below that deleted + m_sets.setAutoDelete(false);//to avoid auto deleting in insert() + const int r = m_dataObject->currentRow(); + for (int i=r;i<int(m_sets.size()-1);i++) { + KoProperty::Set *set = m_sets[i+1]; + m_sets.insert( i , set ); + } + m_sets.insert( m_sets.size()-1, 0 ); + m_sets.setAutoDelete(true);//revert the flag + + m_view->propertySetSwitched(); + emit rowDeleted(); +} + +void KexiDataAwarePropertySet::slotRowsDeleted( const QValueList<int> &rows ) +{ + //let's move most property sets up & delete unwanted + m_sets.setAutoDelete(false);//to avoid auto deleting in insert() + const int orig_size = size(); + int prev_r = -1; + int num_removed = 0, cur_r = -1; + for (QValueList<int>::ConstIterator r_it = rows.constBegin(); r_it!=rows.constEnd() && *r_it < orig_size; ++r_it) { + cur_r = *r_it;// - num_removed; + if (prev_r>=0) { +// kdDebug() << "move " << prev_r+num_removed-1 << ".." << cur_r-1 << " to " << prev_r+num_removed-1 << ".." << cur_r-2 << endl; + int i=prev_r; + KoProperty::Set *set = m_sets.take(i+num_removed); + kdDebug() << "property set " << i+num_removed << " deleted" << endl; + delete set; + num_removed++; + for (; (i+num_removed)<cur_r; i++) { + m_sets.insert( i, m_sets[i+num_removed] ); + kdDebug() << i << " <- " << i+num_removed << endl; + } + } + prev_r = cur_r - num_removed; + } + //move remaining property sets up + if (cur_r>=0) { + KoProperty::Set *set = m_sets.take(cur_r); + kdDebug() << "property set " << cur_r << " deleted" << endl; + delete set; + num_removed++; + for (int i=prev_r; (i+num_removed)<orig_size; i++) { + m_sets.insert( i, m_sets[i+num_removed] ); + kdDebug() << i << " <- " << i+num_removed << endl; + } + } + //finally: clear last rows + for (int i=orig_size-num_removed; i<orig_size; i++) { + kdDebug() << i << " <- zero" << endl; + m_sets.insert( i, 0 ); + } + m_sets.setAutoDelete(true);//revert the flag + + if (num_removed>0) + m_view->setDirty(); + m_view->propertySetSwitched(); +} + +//void KexiDataAwarePropertySet::slotEmptyRowInserted(KexiTableItem*, uint /*index*/) +void KexiDataAwarePropertySet::slotRowInserted(KexiTableItem*, uint row, bool /*repaint*/) +{ + m_view->setDirty(); + + //let's move down all property set that are below + m_sets.setAutoDelete(false);//to avoid auto deleting in insert() +// const int r = m_dataObject->currentRow(); + m_sets.resize(m_sets.size()+1); + for (int i=int(m_sets.size())-1; i>(int)row; i--) { + KoProperty::Set *set = m_sets[i-1]; + m_sets.insert( i , set ); + } + m_sets.insert( row, 0 ); + m_sets.setAutoDelete(true);//revert the flag + + m_view->propertySetSwitched(); + + emit rowInserted(); +} + +void KexiDataAwarePropertySet::slotCellSelected(int, int row) +{ + if(row == m_row) + return; + m_row = row; + m_view->propertySetSwitched(); +} + +KoProperty::Set* KexiDataAwarePropertySet::findPropertySetForItem(KexiTableItem& item) +{ + if (m_currentTVData.isNull()) + return 0; + int idx = m_currentTVData->findRef(&item); + if (idx<0) + return 0; + return m_sets[idx]; +} + +int KexiDataAwarePropertySet::findRowForPropertyValue(const QCString& propertyName, const QVariant& value) +{ + const int size = m_sets.size(); + for (int i=0; i<size; i++) { + KoProperty::Set *set = m_sets[i]; + if (!set || !set->contains(propertyName)) + continue; + if (set->property(propertyName).value() == value) + return i; + } + return -1; +} + +#include "kexidataawarepropertyset.moc" diff --git a/kexi/widget/tableview/kexidataawarepropertyset.h b/kexi/widget/tableview/kexidataawarepropertyset.h new file mode 100644 index 00000000..cee55da0 --- /dev/null +++ b/kexi/widget/tableview/kexidataawarepropertyset.h @@ -0,0 +1,149 @@ +/* This file is part of the KDE project + Copyright (C) 2004-2005 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. +*/ + +#ifndef KEXIDATAAWAREPROPERTYSET_H +#define KEXIDATAAWAREPROPERTYSET_H + +#include <qguardedptr.h> +#include <qptrvector.h> +#include <koproperty/set.h> + +typedef QPtrVector<KoProperty::Set> SetVector; + +class KexiViewBase; +class KexiTableItem; +class KexiTableViewData; +class KexiDataAwareObjectInterface; + +/*! This helper class handles data changes of a single + object implementing KexiDataAwareObjectInterface (e.g. KexiTableView) inside + a KexiViewBase container. + + It is currently used in KexiAlterTableDialog and KexiQueryDesignerGuiEditor, + and may be used for similar purposes, when each KexiDataAwareObjectInterface's + row can be associated with single KoProperty::Set object, and given + KexiDataAwareObjectInterface object has to inform the world about currently + selected row/property set. + + Following functionality is built-in: + - auto-initializing after resetting of table view's data + - destroying single property set that is associated with deleted row + - inserting single property set that and associating it with new row + - all property sets are cleared when view's data is cleared (using clear()) + - setting view's 'dirty' flag when needed + - signalling via KexiViewBase::propertySetSwitched() that current property + set has changed (e.g. on moving to other row) +*/ +class KEXIDATATABLE_EXPORT KexiDataAwarePropertySet : public QObject +{ + Q_OBJECT + + public: + /*! You can instantiate KexiDataAwarePropertySet object + for existing \a tableView and \a view. \a tableView can have data assigned + (KexiDataAwareObjectInterface::setData()) now but it can be done later as well + (but assigning data is needed for proper functionality). + Any changed reassignments of table view's data will be handled automatically. */ + KexiDataAwarePropertySet(KexiViewBase *view, KexiDataAwareObjectInterface* dataObject); + + virtual ~KexiDataAwarePropertySet(); + + uint size() const; + + KoProperty::Set* currentPropertySet() const; + + uint currentRow() const; + + inline KoProperty::Set* at(uint row) const { return m_sets[row]; } + + /*! \return a pointer to property set assigned for \a item or null if \a item has no + property set assigned or it's not owned by assigned table view or + if assigned table view has no data set. */ + KoProperty::Set* findPropertySetForItem(KexiTableItem& item); + + /*! \return number of the first row containing \a propertyName property equal to \a value. + This is used e.g. in the Table Designer to find a row by field name. + If no such row has been found, -1 is returned. */ + int findRowForPropertyValue(const QCString& propertyName, const QVariant& value); + + signals: + /*! Emmited when row is deleted. + KexiDataAwareObjectInterface::rowDeleted() signal is usually used but when you're using + KexiDataAwarePropertySet, you never know if currentPropertySet() is updated. + So use this signal instead. */ + void rowDeleted(); + + /*! Emmited when row is inserted. + Purpose of this signal is similar to rowDeleted() signal. */ + void rowInserted(); + + public slots: + void removeCurrentPropertySet(); + + void clear(uint minimumSize = 0); + + /*! Inserts \a set property set at \a row position. + If there was a buffer at this position before, it will be destroyed. + If \a newOne is true, the property set will be marked as newly created, + simply by adding "newrow" property. + + The property set \a set will be owned by this object, so you should not + delete this property set by hand but call removeCurrentPropertySet() + or remove(uint) instead. + Note that property set's parent (QObject::parent()) must be null + or qual to this KexiDataAwarePropertySet object, otherwise this method + will fail with a warning. + */ + void insert(uint row, KoProperty::Set* set, bool newOne = false); + + /*! Removes a property set at \a row position. */ + void remove(uint row); + + protected slots: + /*! Handles table view's data source changes. */ + void slotDataSet( KexiTableViewData *data ); + + //! Called on row delete in a tableview. + void slotRowDeleted(); + + //! Called on multiple rows delete in a tableview. + void slotRowsDeleted( const QValueList<int> &rows ); + + //! Called on \a row insertion in a tableview. + void slotRowInserted(KexiTableItem* item, uint row, bool repaint); + + //! Called on selecting another cell in a tableview. + void slotCellSelected(int, int row); + + //! Called on clearing tableview's data: just clears all property sets. + void slotReloadRequested(); + + protected: + SetVector m_sets; //!< prop. sets vector + + QGuardedPtr<KexiViewBase> m_view; + KexiDataAwareObjectInterface* m_dataObject; +// QGuardedPtr<KexiTableView> m_tableView; + QGuardedPtr<KexiTableViewData> m_currentTVData; + + int m_row; //!< used to know if a new row is selected in slotCellSelected() +}; + +#endif + diff --git a/kexi/widget/tableview/kexidatatableview.cpp b/kexi/widget/tableview/kexidatatableview.cpp new file mode 100644 index 00000000..9248e890 --- /dev/null +++ b/kexi/widget/tableview/kexidatatableview.cpp @@ -0,0 +1,121 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Lucijan Busch <lucijan@kde.org> + Copyright (C) 2003 Joseph Wenninger <jowenn@kde.org> + Copyright (C) 2003-2004 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 <qtimer.h> +#include <qapplication.h> + +#include <kmessagebox.h> +#include <klocale.h> +#include <kdebug.h> +#include <kaction.h> + +#include <kexidb/connection.h> +#include <kexidb/cursor.h> + +#include "kexidatatableview.h" + + +KexiDataTableView::KexiDataTableView(QWidget *parent, const char *name) + : KexiTableView(0, parent, name) +{ + init(); +} + +KexiDataTableView::KexiDataTableView(QWidget *parent, const char *name, KexiDB::Cursor *cursor) + : KexiTableView(0, parent, name) +{ + init(); + setData(cursor); +} + +KexiDataTableView::~KexiDataTableView() +{ +} + +void +KexiDataTableView::init() +{ + m_cursor = 0; + +// m_maxRecord = 0; +// m_records = 0; +// m_first = false; + +// connect(this, SIGNAL(contentsMoving(int, int)), this, SLOT(slotMoving(int))); +// connect(verticalScrollBar(), SIGNAL(sliderMoved(int)), this, SLOT(slotMoving(int))); +} + +/*void KexiDataTableView::initActions(KActionCollection *col) +{ + KexiTableView::initActions(col); + new KAction(i18n("Filter"), "filter", 0, this, SLOT(filter()), col, "tablepart_filter"); +}*/ + +bool KexiDataTableView::setData(KexiDB::Cursor *cursor) +{ +//js if (!m_first) +//js clearColumns(); + if (!cursor) { + clearColumns(); + m_cursor = 0; + return true; + } + if (cursor!=m_cursor) { + clearColumns(); + } + m_cursor = cursor; + + if (!m_cursor->query()) { + kdDebug() << "KexiDataTableView::setData(): WARNING: cursor should have query schema defined!\n--aborting setData()." << endl; + m_cursor->debug(); + clearColumns(); + return false; + } + + if (m_cursor->fieldCount()<1) { + clearColumns(); + return true; + } + + if (!m_cursor->isOpened() && !m_cursor->open()) { + kdDebug() << "KexiDataTableView::setData(): WARNING: cannot open cursor\n--aborting setData(). \n" << + m_cursor->serverErrorMsg() << endl; + m_cursor->debug(); + clearColumns(); + return false; + } + + KexiTableViewData *tv_data = new KexiTableViewData(m_cursor); + + QString caption = m_cursor->query()->caption(); + if (caption.isEmpty()) + caption = m_cursor->query()->name(); + + setCaption( caption ); + + //PRIMITIVE!! data setting: + tv_data->preloadAllRows(); + + KexiTableView::setData(tv_data); + return true; +} + +#include "kexidatatableview.moc" diff --git a/kexi/widget/tableview/kexidatatableview.h b/kexi/widget/tableview/kexidatatableview.h new file mode 100644 index 00000000..6f421894 --- /dev/null +++ b/kexi/widget/tableview/kexidatatableview.h @@ -0,0 +1,94 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Lucijan Busch <lucijan@kde.org> + Copyright (C) 2003 Joseph Wenninger <jowenn@kde.org> + Copyright (C) 2003-2004 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. + */ + +#ifndef KEXIDATATABLEVIEW_H +#define KEXIDATATABLEVIEW_H + +#include "kexitableview.h" + +class KexiTableItem; +class QVariant; +class KXMLGUIClient; + +namespace KexiDB { + class Cursor; +} + +/** + * Database aware table widget. + */ +class KEXIDATATABLE_EXPORT KexiDataTableView : public KexiTableView +{ + Q_OBJECT + + public: + /** + * creates a blank widget + */ + KexiDataTableView(QWidget *parent, const char *name =0); + + /*! Creates a table widget and fills it using data from \a cursor. + Cursor will be opened (with open()) if it is not yet opened. + Cursor must be defined on query schema, not raw statement (see Connection::prepareQuery() + and Connection::executeQuery()), otherwise the table view remain not filled with data. + Cursor \a cursor will not be owned by this object. + */ + KexiDataTableView(QWidget *parent, const char *name, KexiDB::Cursor *cursor); + + ~KexiDataTableView(); + +// virtual void initActions(KActionCollection *col); + + /*! Fills table view with data using \a cursor. \return true on success. + Cursor \a cursor will not be owned by this object. */ + bool setData(KexiDB::Cursor *cursor); + + /*! \return cursor used as data source for this table view, + or NULL if no valid cursor is defined. */ + KexiDB::Cursor *cursor() { return m_cursor; } + + /** + * @returns the number of records in the data set, (if data set is present) + * @note not all of the records have to be processed + */ + int recordCount() { return m_data->count(); } + + #ifndef KEXI_NO_PRINT +// virtual void print(KPrinter &printer); + #endif + + protected: + void init(); + + /*! Reimplemented: called by deleteItem() - we are deleting data associated with \a item. */ +// virtual bool beforeDeleteItem(KexiTableItem *item); + + protected slots: +// void slotClearData(); + + private: + //db stuff + KexiDB::Cursor *m_cursor; + +// QMap<KexiDBUpdateRecord*,KexiTableItem*> m_insertMapping; +}; + +#endif diff --git a/kexi/widget/tableview/kexidatetableedit.cpp b/kexi/widget/tableview/kexidatetableedit.cpp new file mode 100644 index 00000000..8a1fbcae --- /dev/null +++ b/kexi/widget/tableview/kexidatetableedit.cpp @@ -0,0 +1,290 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Lucijan Busch <lucijan@gmx.at> + Copyright (C) 2003 Daniel Molkentin <molkentin@kde.org> + Copyright (C) 2003-2004,2006 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 "kexidatetableedit.h" + +#include <qapplication.h> +#include <qpainter.h> +#include <qvariant.h> +#include <qrect.h> +#include <qpalette.h> +#include <qcolor.h> +#include <qfontmetrics.h> +#include <qdatetime.h> +#include <qcursor.h> +#include <qpoint.h> +#include <qlayout.h> +#include <qtoolbutton.h> +#include <qdatetimeedit.h> +#include <qclipboard.h> + +#include <kdebug.h> +#include <klocale.h> +#include <kglobal.h> +#include <kdatepicker.h> +#include <kdatetbl.h> +#include <klineedit.h> +#include <kpopupmenu.h> +#include <kdatewidget.h> + +#include <kexiutils/utils.h> + + +KexiDateTableEdit::KexiDateTableEdit(KexiTableViewColumn &column, QWidget *parent) + : KexiInputTableEdit(column, parent) +{ + setName("KexiDateTableEdit"); + +//! @todo add QValidator so date like "2006-59-67" cannot be even entered + + m_lineedit->setInputMask( m_formatter.inputMask() ); +} + +KexiDateTableEdit::~KexiDateTableEdit() +{ +} + +void KexiDateTableEdit::setValueInInternalEditor(const QVariant &value) +{ + if (value.isValid() && value.toDate().isValid()) + m_lineedit->setText( m_formatter.dateToString( value.toDate() ) ); + else + m_lineedit->setText( QString::null ); +} + +void KexiDateTableEdit::setValueInternal(const QVariant& add_, bool removeOld) +{ + if (removeOld) { + //new date entering... just fill the line edit +//! @todo cut string if too long.. + QString add(add_.toString()); + m_lineedit->setText(add); + m_lineedit->setCursorPosition(add.length()); + return; + } + setValueInInternalEditor( m_origValue ); + m_lineedit->setCursorPosition(0); //ok? +} + +void KexiDateTableEdit::setupContents( QPainter *p, bool focused, const QVariant& val, + QString &txt, int &align, int &x, int &y_offset, int &w, int &h ) +{ + Q_UNUSED(p); + Q_UNUSED(focused); + Q_UNUSED(x); + Q_UNUSED(w); + Q_UNUSED(h); +#ifdef Q_WS_WIN + y_offset = -1; +#else + y_offset = 0; +#endif + if (val.toDate().isValid()) + txt = m_formatter.dateToString(val.toDate()); +// txt = val.toDate().toString(Qt::LocalDate); + align |= AlignLeft; +} + +bool KexiDateTableEdit::valueIsNull() +{ +// if (m_lineedit->text().replace(m_formatter.separator(),"").stripWhiteSpace().isEmpty()) + if (m_formatter.isEmpty(m_lineedit->text())) //empty date is null + return true; + return dateValue().isNull(); +} + +bool KexiDateTableEdit::valueIsEmpty() +{ + return valueIsNull();//js OK? TODO (nonsense?) +} + +QDate KexiDateTableEdit::dateValue() const +{ + return m_formatter.stringToDate( m_lineedit->text() ); +} + +QVariant KexiDateTableEdit::value() +{ + return m_formatter.stringToVariant( m_lineedit->text() ); +} + +bool KexiDateTableEdit::valueIsValid() +{ + if (m_formatter.isEmpty(m_lineedit->text())) //empty date is valid + return true; + return m_formatter.stringToDate( m_lineedit->text() ).isValid(); +} + +void KexiDateTableEdit::handleCopyAction(const QVariant& value, const QVariant& visibleValue) +{ + Q_UNUSED(visibleValue); + if (!value.isNull() && value.toDate().isValid()) + qApp->clipboard()->setText( m_formatter.dateToString(value.toDate()) ); + else + qApp->clipboard()->setText( QString::null ); +} + +void KexiDateTableEdit::handleAction(const QString& actionName) +{ + const bool alreadyVisible = m_lineedit->isVisible(); + + if (actionName=="edit_paste") { + const QVariant newValue( m_formatter.stringToDate(qApp->clipboard()->text()) ); + if (!alreadyVisible) { //paste as the entire text if the cell was not in edit mode + emit editRequested(); + m_lineedit->clear(); + } + setValueInInternalEditor( newValue ); + } + else + KexiInputTableEdit::handleAction(actionName); +} + +/* +void +KexiDateTableEdit::slotDateChanged(QDate date) +{ + m_edit->setDate(date); + repaint(); +} + +void +KexiDateTableEdit::slotShowDatePicker() +{ + QDate date = m_edit->date(); + + m_datePicker->setDate(date); + m_datePicker->setFocus(); + m_datePicker->show(); + m_datePicker->setFocus(); +} + +//! @internal helper +void KexiDateTableEdit::moveToFirstSection() +{ + if (!m_dte_date_obj) + return; +#ifdef QDateTimeEditor_HACK + if (m_dte_date) + m_dte_date->setFocusSection(0); +#else +#ifdef Q_WS_WIN //tmp + QKeyEvent ke_left(QEvent::KeyPress, Qt::Key_Left, 0, 0); + for (int i=0; i<8; i++) + QApplication::sendEvent( m_dte_date_obj, &ke_left ); +#endif +#endif +} + +bool KexiDateTableEdit::eventFilter( QObject *o, QEvent *e ) +{ + if (o==m_datePicker) { + kdDebug() << e->type() << endl; + switch (e->type()) { + case QEvent::Hide: + m_datePickerPopupMenu->hide(); + break; + case QEvent::KeyPress: + case QEvent::KeyRelease: { + kdDebug() << "ok!" << endl; + QKeyEvent *ke = (QKeyEvent *)e; + if (ke->key()==Key_Enter || ke->key()==Key_Return) { + //accepting picker + acceptDate(); + return true; + } + else if (ke->key()==Key_Escape) { + //canceling picker + m_datePickerPopupMenu->hide(); + kdDebug() << "reject" << endl; + return true; + } + else m_datePickerPopupMenu->setFocus(); + break; + } + default: + break; + } + } +#ifdef Q_WS_WIN //tmp + else if (e->type()==QEvent::FocusIn && o->parent() && o->parent()->parent()==m_edit + && m_setNumberOnFocus >= 0 && m_dte_date_obj) + { + // there was a number character passed as 'add' parameter in init(): + moveToFirstSection(); + QKeyEvent ke(QEvent::KeyPress, int(Qt::Key_0)+m_setNumberOnFocus, + '0'+m_setNumberOnFocus, 0, QString::number(m_setNumberOnFocus)); + QApplication::sendEvent( m_dte_date_obj, &ke ); + m_setNumberOnFocus = -1; + } +#endif +#ifdef QDateTimeEditor_HACK + else if (e->type()==QEvent::KeyPress && m_dte_date) { + QKeyEvent *ke = static_cast<QKeyEvent*>(e); + if ((ke->key()==Qt::Key_Right && !m_sentEvent && cursorAtEnd()) + || (ke->key()==Qt::Key_Left && !m_sentEvent && cursorAtStart())) + { + //the editor should send this key event: + m_sentEvent = true; //avoid recursion + QApplication::sendEvent( this, ke ); + m_sentEvent = false; + ke->ignore(); + return true; + } + } +#endif + return false; +} + +void KexiDateTableEdit::acceptDate() +{ + m_edit->setDate(m_datePicker->date()); + m_datePickerPopupMenu->hide(); + kdDebug() << "accept" << endl; +} + +bool KexiDateTableEdit::cursorAtStart() +{ +#ifdef QDateTimeEditor_HACK + return m_dte_date && m_edit->hasFocus() && m_dte_date->focusSection()==0; +#else + return false; +#endif +} + +bool KexiDateTableEdit::cursorAtEnd() +{ +#ifdef QDateTimeEditor_HACK + return m_dte_date && m_edit->hasFocus() + && m_dte_date->focusSection()==int(m_dte_date->sectionCount()-1); +#else + return false; +#endif +} + +void KexiDateTableEdit::clear() +{ + m_edit->setDate(QDate()); +}*/ + +KEXI_CELLEDITOR_FACTORY_ITEM_IMPL(KexiDateEditorFactoryItem, KexiDateTableEdit) + +#include "kexidatetableedit.moc" diff --git a/kexi/widget/tableview/kexidatetableedit.h b/kexi/widget/tableview/kexidatetableedit.h new file mode 100644 index 00000000..4f2a4f59 --- /dev/null +++ b/kexi/widget/tableview/kexidatetableedit.h @@ -0,0 +1,66 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Lucijan Busch <lucijan@gmx.at> + Copyright (C) 2003 Daniel Molkentin <molkentin@kde.org> + Copyright (C) 2003-2004,2006 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. + */ + +#ifndef KEXIDATETABLEEDIT_H +#define KEXIDATETABLEEDIT_H + +#include "kexiinputtableedit.h" +#include <widget/utils/kexidatetimeformatter.h> + +/*! @short Editor class for Date type. + It is a replacement QDateEdit due to usability problems: + people are accustomed to use single-character cursor. + Date format is retrieved from the KDE global settings. + and input/output is performed using KLineEdit (from KexiInputTableEdit). +*/ +class KexiDateTableEdit : public KexiInputTableEdit +{ + Q_OBJECT + + public: + KexiDateTableEdit(KexiTableViewColumn &column, QWidget *parent=0); + virtual ~KexiDateTableEdit(); + virtual void setupContents( QPainter *p, bool focused, const QVariant& val, + QString &txt, int &align, int &x, int &y_offset, int &w, int &h ); + virtual QVariant value(); + virtual bool valueIsNull(); + virtual bool valueIsEmpty(); + virtual bool valueIsValid(); + + /*! Reimplemented after KexiInputTableEdit. */ + virtual void handleAction(const QString& actionName); + + /*! Reimplemented after KexiInputTableEdit. */ + virtual void handleCopyAction(const QVariant& value, const QVariant& visibleValue); + + protected: + //! helper + void setValueInInternalEditor(const QVariant &value); + virtual void setValueInternal(const QVariant& add, bool removeOld); + QDate dateValue() const; + + //! Used to format and convert date values + KexiDateFormatter m_formatter; +}; + +KEXI_DECLARE_CELLEDITOR_FACTORY_ITEM(KexiDateEditorFactoryItem) + +#endif diff --git a/kexi/widget/tableview/kexidatetimetableedit.cpp b/kexi/widget/tableview/kexidatetimetableedit.cpp new file mode 100644 index 00000000..fbca7cd6 --- /dev/null +++ b/kexi/widget/tableview/kexidatetimetableedit.cpp @@ -0,0 +1,165 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Lucijan Busch <lucijan@gmx.at> + Copyright (C) 2003 Daniel Molkentin <molkentin@kde.org> + Copyright (C) 2003-2004,2006 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 "kexidatetimetableedit.h" + +#include <qapplication.h> +#include <qpainter.h> +#include <qvariant.h> +#include <qrect.h> +#include <qpalette.h> +#include <qcolor.h> +#include <qfontmetrics.h> +#include <qdatetime.h> +#include <qcursor.h> +#include <qpoint.h> +#include <qlayout.h> +#include <qtoolbutton.h> +#include <qdatetimeedit.h> +#include <qclipboard.h> + +#include <kdebug.h> +#include <klocale.h> +#include <kglobal.h> +#include <kdatepicker.h> +#include <kdatetbl.h> +#include <klineedit.h> +#include <kpopupmenu.h> +#include <kdatewidget.h> + +#include <kexiutils/utils.h> + +KexiDateTimeTableEdit::KexiDateTimeTableEdit(KexiTableViewColumn &column, QWidget *parent) + : KexiInputTableEdit(column, parent) +{ + setName("KexiDateTimeTableEdit"); + +//! @todo add QValidator so time like "99:88:77" cannot be even entered + + m_lineedit->setInputMask( + dateTimeInputMask( m_dateFormatter, m_timeFormatter ) ); +} + +KexiDateTimeTableEdit::~KexiDateTimeTableEdit() +{ +} + +void KexiDateTimeTableEdit::setValueInInternalEditor(const QVariant &value) +{ + if (value.isValid() && value.toDateTime().isValid()) + m_lineedit->setText( + m_dateFormatter.dateToString( value.toDateTime().date() ) + " " + + m_timeFormatter.timeToString( value.toDateTime().time() ) ); + else + m_lineedit->setText( QString::null ); +} + +void KexiDateTimeTableEdit::setValueInternal(const QVariant& add_, bool removeOld) +{ + if (removeOld) { + //new time entering... just fill the line edit +//! @todo cut string if too long.. + QString add(add_.toString()); + m_lineedit->setText(add); + m_lineedit->setCursorPosition(add.length()); + return; + } + setValueInInternalEditor( m_origValue ); + m_lineedit->setCursorPosition(0); //ok? +} + +void KexiDateTimeTableEdit::setupContents( QPainter *p, bool focused, const QVariant& val, + QString &txt, int &align, int &x, int &y_offset, int &w, int &h ) +{ + Q_UNUSED(p); + Q_UNUSED(focused); + Q_UNUSED(x); + Q_UNUSED(w); + Q_UNUSED(h); +#ifdef Q_WS_WIN + y_offset = -1; +#else + y_offset = 0; +#endif + if (val.toDateTime().isValid()) + txt = m_dateFormatter.dateToString(val.toDateTime().date()) + " " + + m_timeFormatter.timeToString(val.toDateTime().time()); + align |= AlignLeft; +} + +bool KexiDateTimeTableEdit::valueIsNull() +{ + if (textIsEmpty()) + return true; + return !stringToDateTime(m_dateFormatter, m_timeFormatter, m_lineedit->text()).isValid(); +} + +bool KexiDateTimeTableEdit::valueIsEmpty() +{ + return valueIsNull();//js OK? TODO (nonsense?) +} + +QVariant KexiDateTimeTableEdit::value() +{ + if (textIsEmpty()) + return QVariant(); + return stringToDateTime(m_dateFormatter, m_timeFormatter, m_lineedit->text()); +} + +bool KexiDateTimeTableEdit::valueIsValid() +{ + return dateTimeIsValid( m_dateFormatter, m_timeFormatter, m_lineedit->text() ); +} + +bool KexiDateTimeTableEdit::textIsEmpty() const +{ + return dateTimeIsEmpty( m_dateFormatter, m_timeFormatter, m_lineedit->text() ); +} + +void KexiDateTimeTableEdit::handleCopyAction(const QVariant& value, const QVariant& visibleValue) +{ + Q_UNUSED(visibleValue); + if (!value.isNull() && value.toDateTime().isValid()) + qApp->clipboard()->setText( m_dateFormatter.dateToString(value.toDateTime().date()) + " " + + m_timeFormatter.timeToString(value.toDateTime().time()) ); + else + qApp->clipboard()->setText( QString::null ); +} + +void KexiDateTimeTableEdit::handleAction(const QString& actionName) +{ + const bool alreadyVisible = m_lineedit->isVisible(); + + if (actionName=="edit_paste") { + const QVariant newValue( stringToDateTime(m_dateFormatter, m_timeFormatter, qApp->clipboard()->text()) ); + if (!alreadyVisible) { //paste as the entire text if the cell was not in edit mode + emit editRequested(); + m_lineedit->clear(); + } + setValueInInternalEditor( newValue ); + } + else + KexiInputTableEdit::handleAction(actionName); +} + +KEXI_CELLEDITOR_FACTORY_ITEM_IMPL(KexiDateTimeEditorFactoryItem, KexiDateTimeTableEdit) + +#include "kexidatetimetableedit.moc" diff --git a/kexi/widget/tableview/kexidatetimetableedit.h b/kexi/widget/tableview/kexidatetimetableedit.h new file mode 100644 index 00000000..c2f9eba8 --- /dev/null +++ b/kexi/widget/tableview/kexidatetimetableedit.h @@ -0,0 +1,69 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Lucijan Busch <lucijan@gmx.at> + Copyright (C) 2003 Daniel Molkentin <molkentin@kde.org> + Copyright (C) 2003-2004,2006 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. + */ + +#ifndef KEXIDATETIMETABLEEDIT_H +#define KEXIDATETIMETABLEEDIT_H + +#include "kexidatetableedit.h" +#include "kexitimetableedit.h" + +/*! @short Editor class for Date/Time type. + It is a replacement QDateTimeEdit due to usability problems: + people are accustomed to use single-character cursor. + Date and Time format is retrieved from the KDE global settings + and input/output is performed using KLineEdit (from KexiInputTableEdit). +*/ +class KexiDateTimeTableEdit : public KexiInputTableEdit +{ + Q_OBJECT + + public: + KexiDateTimeTableEdit(KexiTableViewColumn &column, QWidget *parent=0); + virtual ~KexiDateTimeTableEdit(); + virtual void setupContents( QPainter *p, bool focused, const QVariant& val, + QString &txt, int &align, int &x, int &y_offset, int &w, int &h ); + virtual QVariant value(); + virtual bool valueIsNull(); + virtual bool valueIsEmpty(); + virtual bool valueIsValid(); + + /*! Reimplemented after KexiInputTableEdit. */ + virtual void handleAction(const QString& actionName); + + /*! Reimplemented after KexiInputTableEdit. */ + virtual void handleCopyAction(const QVariant& value, const QVariant& visibleValue); + + protected: + //! helper + void setValueInInternalEditor(const QVariant &value); + virtual void setValueInternal(const QVariant& add, bool removeOld); + bool textIsEmpty() const; + + //! Used to format and convert date values + KexiDateFormatter m_dateFormatter; + + //! Used to format and convert time values + KexiTimeFormatter m_timeFormatter; +}; + +KEXI_DECLARE_CELLEDITOR_FACTORY_ITEM(KexiDateTimeEditorFactoryItem) + +#endif diff --git a/kexi/widget/tableview/kexiinputtableedit.cpp b/kexi/widget/tableview/kexiinputtableedit.cpp new file mode 100644 index 00000000..9af5c627 --- /dev/null +++ b/kexi/widget/tableview/kexiinputtableedit.cpp @@ -0,0 +1,395 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Lucijan Busch <lucijan@gmx.at> + Copyright (C) 2003-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 "kexiinputtableedit.h" + +#include <qregexp.h> +#include <qevent.h> +#include <qlayout.h> +#include <qtimer.h> +#include <qpainter.h> +#include <qapplication.h> +#include <qclipboard.h> +#include <qtooltip.h> + +#include <kglobal.h> +#include <klocale.h> +#include <kdebug.h> +#include <kglobalsettings.h> +#include <kcompletionbox.h> +#include <knumvalidator.h> +#include <kexiutils/longlongvalidator.h> +#include <kexidb/field.h> +#include <kexidb/fieldvalidator.h> + +//! @internal +class MyLineEdit : public KLineEdit +{ + public: + MyLineEdit(QWidget *parent, const char *name) : KLineEdit(parent,name) + {} + protected: + virtual void drawFrame ( QPainter * p ) { + p->setPen( QPen( colorGroup().text() ) ); + QRect r = rect(); + p->moveTo( r.topLeft() ); + p->lineTo( r.topRight() ); + p->lineTo( r.bottomRight() ); + p->lineTo( r.bottomLeft() ); + if (pos().x() == 0) //draw left side only when it is @ the edge + p->lineTo( r.topLeft() ); + } +}; + +//====================================================== + +KexiInputTableEdit::KexiInputTableEdit(KexiTableViewColumn &column, QWidget *parent) + : KexiTableEdit(column, parent) +{ + setName("KexiInputTableEdit"); +// m_type = f.type(); //copied because the rest of code uses m_type +// m_field = &f; +// m_origValue = value;//original value + init(); +} + +KexiInputTableEdit::~KexiInputTableEdit() +{ +} + +void KexiInputTableEdit::init() +{ +// kdDebug() << "KexiInputTableEdit: m_origValue.typeName()==" << m_origValue.typeName() << endl; +// kdDebug() << "KexiInputTableEdit: type== " << field()->typeName() << endl; +// kdDebug() << "KexiInputTableEdit: displayed type== " << displayedField()->typeName() << endl; + + m_textFormatter.setField( field() ); + + //init settings + m_decsym = KGlobal::locale()->decimalSymbol(); + if (m_decsym.isEmpty()) + m_decsym=".";//default + + const bool align_right = displayedField()->isNumericType(); + + if (!align_right) { + //create layer for internal editor + QHBoxLayout *lyr = new QHBoxLayout(this); + lyr->addSpacing(4); + lyr->setAutoAdd(true); + } + + //create internal editor + m_lineedit = new MyLineEdit(this, "KexiInputTableEdit-KLineEdit"); + setViewWidget(m_lineedit); + if (align_right) + m_lineedit->setAlignment(AlignRight); +// m_cview->setFrame(false); +// m_cview->setFrameStyle( QFrame::Plain | QFrame::Box ); +// m_cview->setLineWidth( 1 ); + m_calculatedCell = false; + +#if 0 //js TODO + connect(m_cview->completionBox(), SIGNAL(activated(const QString &)), + this, SLOT(completed(const QString &))); + connect(m_cview->completionBox(), SIGNAL(highlighted(const QString &)), + this, SLOT(completed(const QString &))); + m_cview->completionBox()->setTabHandling(true); +#endif + +} + +void KexiInputTableEdit::setValueInternal(const QVariant& add, bool removeOld) +{ + QString text( m_textFormatter.valueToText(removeOld ? QVariant() : m_origValue, add.toString()) ); + if (text.isEmpty()) { + if (m_origValue.toString().isEmpty()) { + //we have to set NULL initial value: + m_lineedit->setText(QString::null); + } + } + else { + m_lineedit->setText(text); + } + +#if 0 +//move to end is better by default + m_cview->selectAll(); +#else +//js TODO: by default we're moving to the end of editor, ADD OPTION allowing "select all chars" + m_lineedit->end(false); +#endif + + if (!m_lineedit->validator()) { + QValidator *validator = new KexiDB::FieldValidator( + *field(), m_lineedit, "KexiInputTableEdit-validator"); + m_lineedit->setValidator( validator ); + } +} + +#if 0 +//moved to KexiTextFormatter +QString KexiInputTableEdit::valueToText(KexiDB::Field* field, const QVariant& value, const QString& add) +{ + QString text; //result + + if (field->isFPNumericType()) { +//! @todo precision! +//! @todo support 'g' format + text = QString::number(value.toDouble(), 'f', + QMAX(field->visibleDecimalPlaces(), 10)); //<-- 10 is quite good maximum for fractional digits + //! @todo add command line settings? + if (value.toDouble() == 0.0) { + text = add.isEmpty() ? "0" : add; //eat 0 + } + else { +//! @todo (js): get decimal places settings here... + QStringList sl = QStringList::split(".", text); + if (text.isEmpty()) { + //nothing + } + else if (sl.count()==2) { +// kdDebug() << "sl.count()=="<<sl.count()<< " " <<sl[0] << " | " << sl[1] << endl; + const QString sl1 = sl[1]; + int pos = sl1.length()-1; + if (pos>=1) { + for (;pos>=0 && sl1[pos]=='0';pos--) + ; + pos++; + } + if (pos>0) + text = sl[0] + m_decsym + sl1.left(pos); + else + text = sl[0]; //no decimal point + } + text += add; + } +/*moved to KexiDB::FieldValidator + if (setValidator && !m_lineedit->validator()) { + QValidator *validator = new KDoubleValidator(m_lineedit); + m_lineedit->setValidator( validator ); + }*/ + } + else { + text = value.toString(); + if (field->isIntegerType()) { + if (value.toInt() == 0) { + text = add; //eat 0 + } + else { + text += add; + } +/*moved to KexiDB::FieldValidator +//! @todo implement ranges here! + if (setValidator && !m_lineedit->validator()) { + QValidator *validator; + if (KexiDB::Field::BigInteger == field()->type()) { +//! @todo use field->isUnsigned() for KexiUtils::ULongLongValidator + validator = new KexiUtils::LongLongValidator(m_lineedit); + } + else { + validator = new KIntValidator(m_lineedit); + } + m_lineedit->setValidator( validator ); + }*/ + } + else {//default: text + text += add; + } + } + + return text; +} +#endif + +void KexiInputTableEdit::paintEvent ( QPaintEvent * /*e*/ ) +{ + QPainter p(this); + p.setPen( QPen( colorGroup().text() ) ); + p.drawRect( rect() ); +} + +void +KexiInputTableEdit::setRestrictedCompletion() +{ +#if 0 //js TODO +kdDebug() << "KexiInputTableEdit::setRestrictedCompletion()" << endl; +// KLineEdit *content = static_cast<KLineEdit*>(m_view); + if(m_cview->text().isEmpty()) + return; + + kdDebug() << "KexiInputTableEdit::setRestrictedCompletion(): something to do" << endl; + + m_cview->useGlobalKeyBindings(); + + QStringList newC; + QStringList::ConstIterator it, end( m_comp.constEnd() ); + for( it = m_comp.constBegin(); it != end; ++it) + { + if((*it).startsWith(m_cview->text())) + newC.append(*it); + } + m_cview->setCompletedItems(newC); +#endif +} + +void +KexiInputTableEdit::completed(const QString &s) +{ +// kdDebug() << "KexiInputTableEdit::completed(): " << s << endl; + m_lineedit->setText(s); +} + +bool KexiInputTableEdit::valueChanged() +{ + //not needed? if (m_lineedit->text()!=m_origValue.toString()) + //not needed? return true; + return KexiTableEdit::valueChanged(); +} + +bool KexiInputTableEdit::valueIsNull() +{ + return m_lineedit->text().isNull(); +} + +bool KexiInputTableEdit::valueIsEmpty() +{ + return !m_lineedit->text().isNull() && m_lineedit->text().isEmpty(); +} + +QVariant KexiInputTableEdit::value() +{ + if (field()->isFPNumericType()) {//==KexiDB::Field::Double || m_type==KexiDB::Field::Float) { + //! js @todo PRESERVE PRECISION! + QString txt = m_lineedit->text(); + if (m_decsym!=".") + txt = txt.replace(m_decsym,".");//convert back + bool ok; + const double result = txt.toDouble(&ok); + return ok ? QVariant(result) : QVariant(); + } + else if (field()->isIntegerType()) { +//! @todo check constraints + bool ok; + if (KexiDB::Field::BigInteger == field()->type()) { + if (field()->isUnsigned()) { + const Q_ULLONG result = m_lineedit->text().toULongLong(&ok); + return ok ? QVariant(result) : QVariant(); + } + else { + const Q_LLONG result = m_lineedit->text().toLongLong(&ok); + return ok ? QVariant(result) : QVariant(); + } + } + if (KexiDB::Field::Integer == field()->type()) { + if (field()->isUnsigned()) { + const uint result = m_lineedit->text().toUInt(&ok); + return ok ? QVariant(result) : QVariant(); + } + } + //default: signed int + const int result = m_lineedit->text().toInt(&ok); + return ok ? QVariant(result) : QVariant(); + } + //default: text + return m_lineedit->text(); +} + +void +KexiInputTableEdit::clear() +{ + m_lineedit->clear(); +} + +bool KexiInputTableEdit::cursorAtStart() +{ + return m_lineedit->cursorPosition()==0; +} + +bool KexiInputTableEdit::cursorAtEnd() +{ + return m_lineedit->cursorPosition()==(int)m_lineedit->text().length(); +} + +QSize KexiInputTableEdit::totalSize() +{ + if (!m_lineedit) + return size(); + return m_lineedit->size(); +} + +void KexiInputTableEdit::handleCopyAction(const QVariant& value, const QVariant& visibleValue) +{ + Q_UNUSED(visibleValue); +//! @todo handle rich text? + qApp->clipboard()->setText( m_textFormatter.valueToText(value, QString::null) ); +} + +void KexiInputTableEdit::handleAction(const QString& actionName) +{ + const bool alreadyVisible = m_lineedit->isVisible(); + + if (actionName=="edit_paste") { + if (!alreadyVisible) { //paste as the entire text if the cell was not in edit mode + emit editRequested(); + m_lineedit->clear(); + } + m_lineedit->paste(); + } + else if (actionName=="edit_cut") { +//! @todo handle rich text? + if (!alreadyVisible) { //cut the entire text if the cell was not in edit mode + emit editRequested(); + m_lineedit->selectAll(); + } + m_lineedit->cut(); + } +} + +bool KexiInputTableEdit::showToolTipIfNeeded(const QVariant& value, const QRect& rect, + const QFontMetrics& fm, bool focused) +{ + QString text( value.type()==QVariant::String ? value.toString() + : m_textFormatter.valueToText(value, QString::null) ); + QRect internalRect(rect); + internalRect.setLeft(rect.x()+leftMargin()); + internalRect.setWidth(internalRect.width()-rightMargin(focused)-2*3); + kexidbg << rect << " " << internalRect << " " << fm.width(text) << endl; + return fm.width(text) > internalRect.width(); +} + +void KexiInputTableEdit::moveCursorToEnd() +{ + m_lineedit->end(false/*!mark*/); +} + +void KexiInputTableEdit::moveCursorToStart() +{ + m_lineedit->home(false/*!mark*/); +} + +void KexiInputTableEdit::selectAll() +{ + m_lineedit->selectAll(); +} + +KEXI_CELLEDITOR_FACTORY_ITEM_IMPL(KexiInputEditorFactoryItem, KexiInputTableEdit) + +#include "kexiinputtableedit.moc" diff --git a/kexi/widget/tableview/kexiinputtableedit.h b/kexi/widget/tableview/kexiinputtableedit.h new file mode 100644 index 00000000..df770287 --- /dev/null +++ b/kexi/widget/tableview/kexiinputtableedit.h @@ -0,0 +1,126 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Lucijan Busch <lucijan@gmx.at> + Copyright (C) 2003-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. + */ + +#ifndef KEXIINPUTTABLEEDIT_H +#define KEXIINPUTTABLEEDIT_H + +#include <klineedit.h> +#include <qvariant.h> + +#include "kexitableedit.h" +#include "kexicelleditorfactory.h" +#include "kexitextformatter.h" + +/*! @short General purpose cell editor using line edit widget. +*/ +class KEXIDATATABLE_EXPORT KexiInputTableEdit : public KexiTableEdit +{ + Q_OBJECT + + public: + KexiInputTableEdit(KexiTableViewColumn &column, QWidget *parent=0); + + virtual ~KexiInputTableEdit(); + +#if 0 +//moved to KexiTextFormatter + /*! \return text for \a value and \a field. + \a add is a text that should be added to the value if possible. + Used in setValueInternal(), by form widgets and for reporting/printing. */ + static QString valueToText(KexiDB::Field* field, const QVariant& value, const QString& add); +#endif + + virtual bool valueChanged(); + + //! \return true if editor's value is null (not empty) + virtual bool valueIsNull(); + + //! \return true if editor's value is empty (not null). + //! Only few field types can accept "EMPTY" property + //! (check this with KexiDB::Field::hasEmptyProperty()), + virtual bool valueIsEmpty(); + + virtual QVariant value(); + + virtual bool cursorAtStart(); + virtual bool cursorAtEnd(); + +// virtual bool eventFilter(QObject* watched, QEvent* e); +//js void end(bool mark); +//js void backspace(); + virtual void clear(); + + /*! \return total size of this editor, including any buttons, etc. (if present). */ + virtual QSize totalSize(); + + /*! Handles action having standard name \a actionName. + Action could be: "edit_cut", "edit_paste", etc. */ + virtual void handleAction(const QString& actionName); + + /*! Handles copy action for value. The \a value is copied to clipboard in format appropriate + for the editor's impementation, e.g. for image cell it can be a pixmap. + \a visibleValue is unused here. Reimplemented after KexiTableEdit. */ + virtual void handleCopyAction(const QVariant& value, const QVariant& visibleValue); + + /*! Shows a special tooltip for \a value if needed, i.e. if the value could not fit inside \a rect + for a given font metrics \a fm. + \return true a normal tooltip should be displayed (using QToolTip,) and false if + no tooltip should be displayed or a custom tooltip was displayed internally (not yet supported). + This implementation converts the value to text using valueToText() if \a calue is not string to see + whether it can fit inside the cell's \a rect. + If the cell is currentl focused (selected), \a focused is true. */ + virtual bool showToolTipIfNeeded(const QVariant& value, const QRect& rect, const QFontMetrics& fm, + bool focused); + + public slots: + //! Implemented for KexiDataItemInterface + virtual void moveCursorToEnd(); + + //! Implemented for KexiDataItemInterface + virtual void moveCursorToStart(); + + //! Implemented for KexiDataItemInterface + virtual void selectAll(); + + protected slots: + void setRestrictedCompletion(); + void completed(const QString &); + + protected: + //! initializes this editor with \a add value + virtual void setValueInternal(const QVariant& add, bool removeOld); + + void showHintButton(); + void init(); + virtual void paintEvent( QPaintEvent *e ); + + KexiTextFormatter m_textFormatter; + bool m_calculatedCell; + QString m_decsym; //! decimal symbol + QString m_origText; //! orig. Line Edit's text after conversion - for easy comparing + KLineEdit *m_lineedit; + + signals: + void hintClicked(); +}; + +KEXI_DECLARE_CELLEDITOR_FACTORY_ITEM(KexiInputEditorFactoryItem) + +#endif diff --git a/kexi/widget/tableview/kexitableedit.cpp b/kexi/widget/tableview/kexitableedit.cpp new file mode 100644 index 00000000..8c3f5612 --- /dev/null +++ b/kexi/widget/tableview/kexitableedit.cpp @@ -0,0 +1,237 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Peter Simonsson <psn@linux.se> + Copyright (C) 2003-2006 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 "kexitableedit.h" +#include "kexidataawareobjectiface.h" +#include <kexidb/field.h> +#include <kexidb/utils.h> + +#include <qpalette.h> +#include <qpainter.h> + +#include <kglobal.h> +#include <klocale.h> +#include <kdebug.h> + +KexiTableEdit::KexiTableEdit(KexiTableViewColumn &column, QWidget* parent) +: QWidget(dynamic_cast<QScrollView*>(parent) ? dynamic_cast<QScrollView*>(parent)->viewport() : parent) + ,m_column(&column) +// ,m_field(&f) +// ,m_type(f.type()) //copied because the rest of code uses m_type + ,m_scrollView(dynamic_cast<QScrollView*>(parent)) + ,m_usesSelectedTextColor(true) + ,m_view(0) +// ,m_hasFocusableWidget(true) +// ,m_acceptEditorAfterDeleteContents(false) +{ + setPaletteBackgroundColor( palette().color(QPalette::Active, QColorGroup::Base) ); + installEventFilter(this); + + //margins + if (displayedField()->isFPNumericType()) { +#ifdef Q_WS_WIN + m_leftMargin = 0; +#else + m_leftMargin = 0; +#endif + } + else if (displayedField()->isIntegerType()) { +#ifdef Q_WS_WIN + m_leftMargin = 1; +#else + m_leftMargin = 0; +#endif + } + else {//default +#ifdef Q_WS_WIN + m_leftMargin = 5; +#else + m_leftMargin = 5; +#endif + } + + m_rightMargin = 0; + m_rightMarginWhenFocused = 0; +} + +KexiTableEdit::~KexiTableEdit() +{ +} + +KexiDB::Field *KexiTableEdit::displayedField() const +{ + if (m_column->visibleLookupColumnInfo) + return m_column->visibleLookupColumnInfo->field; //mainly for lookup field in KexiComboBoxTableEdit: + + return m_column->field(); //typical case +} + +void KexiTableEdit::setViewWidget(QWidget *v) +{ + m_view = v; + m_view->move(0,0); + m_view->installEventFilter(this); + setFocusProxy(m_view); +} + +void KexiTableEdit::moveChild( QWidget * child, int x, int y ) +{ + if (m_scrollView) + m_scrollView->moveChild(child, x, y); +} + +void KexiTableEdit::resize(int w, int h) +{ + QWidget::resize(w, h); + if (m_view) { + if (!layout()) { //if there is layout (eg. KexiInputTableEdit), resize is automatic + m_view->move(0,0); + m_view->resize(w, h); + } + } +} + +bool +KexiTableEdit::eventFilter(QObject* watched, QEvent* e) +{ +/* if (watched == m_view) { + if(e->type() == QEvent::KeyPress) { + QKeyEvent* ev = static_cast<QKeyEvent*>(e); +// if (ev->key()==Key_Tab) { + +// } + } + }*/ + + if(watched == this) + { + if(e->type() == QEvent::KeyPress) + { + QKeyEvent* ev = static_cast<QKeyEvent*>(e); + + if(ev->key() == Key_Escape) + { + return false; + } + } + else + { + return false; + } + } + return false; +// return QWidget::eventFilter(watched, e); +} + +void KexiTableEdit::paintFocusBorders( QPainter *p, QVariant &, int x, int y, int w, int h ) +{ + p->drawRect(x, y, w, h); +} + +void KexiTableEdit::setupContents( QPainter *p, bool focused, const QVariant& val, + QString &txt, int &align, int &/*x*/, int &y_offset, int &w, int &h ) +{ + Q_UNUSED(p); + Q_UNUSED(focused); + Q_UNUSED(h); + KexiDB::Field *realField = displayedField(); + +#ifdef Q_WS_WIN +// x = 1; + y_offset = -1; +#else +// x = 1; + y_offset = 0; +#endif + + if (realField->isFPNumericType()) { +//! @todo ADD OPTION to displaying NULL VALUES as e.g. "(null)" + if (!val.isNull()) { + txt = KexiDB::formatNumberForVisibleDecimalPlaces( + val.toDouble(), realField->visibleDecimalPlaces()); + } + w -= 6; + align |= AlignRight; + } + else if (realField->isIntegerType()) { + Q_LLONG num = val.toLongLong(); + w -= 6; + align |= AlignRight; + if (!val.isNull()) + txt = QString::number(num); + } + else {//default: + if (!val.isNull()) { + txt = val.toString(); + } + align |= AlignLeft; + } +} + +void KexiTableEdit::paintSelectionBackground( QPainter *p, bool /*focused*/, + const QString& txt, int align, int x, int y_offset, int w, int h, const QColor& fillColor, + const QFontMetrics &fm, bool readOnly, bool fullRowSelection ) +{ + if (!readOnly && !fullRowSelection && !txt.isEmpty()) { + QRect bound=fm.boundingRect(x, y_offset, w - (x+x), h, align, txt); + bound.setY(0); + bound.setWidth( QMIN( bound.width()+2, w - (x+x)+1 ) ); + if (align & Qt::AlignLeft) { + bound.setX(bound.x()-1); + } + else if (align & Qt::AlignRight) { + bound.moveLeft( w - bound.width() ); //move to left, if too wide + } +//TODO align center + bound.setHeight(h-1); + p->fillRect(bound, fillColor); + } + else if (fullRowSelection) { + p->fillRect(0, 0, w, h, fillColor); + } +} + +int KexiTableEdit::widthForValue( QVariant &val, const QFontMetrics &fm ) +{ + return fm.width( val.toString() ); +} + +void KexiTableEdit::repaintRelatedCell() +{ + if (dynamic_cast<KexiDataAwareObjectInterface*>(m_scrollView)) + dynamic_cast<KexiDataAwareObjectInterface*>(m_scrollView)->updateCurrentCell(); +} + +bool KexiTableEdit::showToolTipIfNeeded(const QVariant& value, const QRect& rect, const QFontMetrics& fm, + bool focused) +{ + Q_UNUSED(value); + Q_UNUSED(rect); + Q_UNUSED(fm); + Q_UNUSED(focused); + return false; +} + +int KexiTableEdit::rightMargin(bool focused) const +{ + return focused ? m_rightMarginWhenFocused : m_rightMargin; +} + +#include "kexitableedit.moc" diff --git a/kexi/widget/tableview/kexitableedit.h b/kexi/widget/tableview/kexitableedit.h new file mode 100644 index 00000000..ef38a11f --- /dev/null +++ b/kexi/widget/tableview/kexitableedit.h @@ -0,0 +1,233 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Peter Simonsson <psn@linux.se> + Copyright (C) 2003-2006 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. + */ + +#ifndef _KEXITABLEEDIT_H_ +#define _KEXITABLEEDIT_H_ + +#include <kexidataiteminterface.h> + +#include <qvariant.h> +#include <qscrollview.h> + +#include "kexitableviewdata.h" + +namespace KexiDB { + class Field; + class QueryColumnInfo; +} + +/*! @short Abstract class for a cell editor. + Handles cell painting and displaying the editor widget. +*/ +class KEXIDATATABLE_EXPORT KexiTableEdit : public QWidget, public KexiDataItemInterface +{ + Q_OBJECT + + public: + KexiTableEdit(KexiTableViewColumn &column, QWidget* parent = 0); + + virtual ~KexiTableEdit(); + + //! Implemented for KexiDataItemInterface. + //! \return field information for this item + virtual KexiDB::Field *field() const { return m_column->field(); } + + /*! A rich field information for db-aware data. + For not-db-aware data it is always 0 (use field() instead. */ + virtual KexiDB::QueryColumnInfo *columnInfo() const { return m_column->columnInfo; } + + //! Implemented for KexiDataItemInterface. + //! Does nothing because instead KexiTableViewColumn is used to get field's schema. + virtual void setColumnInfo(KexiDB::QueryColumnInfo *) { } + + //! \return column information for this item + //! (extended information, comparing to field()). + inline KexiTableViewColumn *column() const { return m_column; } + + /*! \return displayed field. This is equal to field() in typical case but can return a different field + definition if the column contains a lookup field. This distiction is especially used for + displaying data dependent on the type and specifics of the field definition + (e.g. text type versus integer type). Note that to compute the editor's value + we still use field(). */ + KexiDB::Field *displayedField() const; + + /*! Reimplemented: resizes a view(). */ + virtual void resize(int w, int h); + + /*! \return the view widget of this editor, e.g. line edit widget. */ + virtual QWidget* widget() { return m_view; } + + /*! Hides item's widget, if available. */ + inline virtual void hideWidget() { hide(); } + + /*! Shows item's widget, if available. */ + inline virtual void showWidget() { show(); } + + /*! Paints a border for the cell described by \a x, \a y, \a w, \a h on \a p painter. + The cell's value is \a val (may be useful if you want to reimplement this method). + */ + virtual void paintFocusBorders( QPainter *p, QVariant &cal, int x, int y, int w, int h ); + + /*! For reimplementation. + Sets up and paints cell's contents using context of \a val value. + \a focused is true if the cell is focused. \a align is set using Qt::AlignmentFlags. + Some additional things may be painted using \a p, + but it is not needed to paint the text (this is done automatically outside of this method). + + Before calling, \a x, \a y_offset, \a w, \a h parameters are initialized, + but you can tune these values depending on the context. + You should set \a txt to a text representation of \a val, + otherwise no text will be painted. + + \a p can be 0 - in this case no painting should be performed, becasue caller only expects + that \a x, \a y_offset, \a w, \a h, \a txt parameters are tuned, if needed. + \a p painter's pen is set to foreground color (usually black) that should be used to paint + foreground information, if needed. For example boolean editor widget paints + a rectangle using this color. */ + virtual void setupContents( QPainter *p, bool focused, const QVariant& val, + QString &txt, int &align, int &x, int &y_offset, int &w, int &h ); + + /*! \return true if "selected text" color should be used to paint contents of the editor. + True by default. It's false e.g. in boolean editor, where no selection is painted + using paintSelectionBackground(). + This flag is set in editor's constructor and checked in KexiTableView::paintCell(). + Depending on it, appropriate ("text" or "selected text" color is set for painter) before + setupContents() is called. */ + bool usesSelectedTextColor() const { return m_usesSelectedTextColor; } + + /*! For reimplementation. + Paints selection's background using \a p. Most parameters are similar to these from + setupContents(). */ + virtual void paintSelectionBackground( QPainter *p, bool focused, const QString& txt, + int align, int x, int y_offset, int w, int h, const QColor& fillColor, + const QFontMetrics &fm, bool readOnly, bool fullRowSelection ); + + /*! Sometimes, editor can contain non-standard margin, for example combobox editor contains + dropdown button at the right side. \return left margin's size; + 0 by default. For reimplementation. */ + int leftMargin() const { return m_leftMargin; } + + /*! Sometimes, editor can contain non-standard margin, for example combobox editor contains + dropdown button at the right side. THe dropdown button's width is counted only if \a focused is true. + \return right margin's size; 0 by default. For reimplementation. */ + int rightMargin(bool focused) const; + + /*! Handles \a ke key event that came over the column that is bound to this editor. + For implementation: true should be returned if \a ke should be accepted. + If \a editorActive is true, this editor is currently active, i.e. the table view is in edit mode. + By default false is returned. */ + virtual bool handleKeyPress( QKeyEvent* ke, bool editorActive ) { + Q_UNUSED(ke); Q_UNUSED(editorActive); return false; } + + /*! Handles double click request coming from the table view. + \return true if it has been consumed. + Reimplemented in KexiBlobTableEdit (to execute "insert file" action. */ + virtual bool handleDoubleClick() { return false; } + + /*! Handles copy action for value. The \a value is copied to clipboard in format appropriate + for the editor's impementation, e.g. for image cell it can be a pixmap. + For a special case (combo box), \a visibleValue can be provided, + so it can be copied to the clipboard instead of unreadable \a value. + For reimplementation. */ + virtual void handleCopyAction(const QVariant& value, const QVariant& visibleValue) = 0; + + /*! \return width of \a value. For the default implementation \a val is converted to a string + and width of this string is returned. */ + virtual int widthForValue( QVariant &val, const QFontMetrics &fm ); + + /*! \return total size of this editor, including any buttons, etc. (if present). + Reimpelment this if you want to return more appropriate size. This impelmentation just + returns QWidget::size(). */ + virtual QSize totalSize() { return QWidget::size(); } + + /*! Shows a special tooltip for \a value if needed, i.e. if the value could not fit inside \a rect + for a given font metrics \a fm. + \return true a normal tooltip should be displayed (using QToolTip,) and false if + no tooltip should be displayed or a custom tooltip was displayed internally (not yet supported). + Default implementation does nothing and returns false. + If the cell is currentl focused (selected), \a focused is true. */ + virtual bool showToolTipIfNeeded(const QVariant& value, const QRect& rect, const QFontMetrics& fm, + bool focused); + + /*! Created internal editor for this editor is needed. This method is only implemented + in KexiComboBoxTableEdit since it's visible value differs from internal value, + so a different KexiTableEdit object is used to displaying the data. */ + virtual void createInternalEditor(KexiDB::QuerySchema& schema) { Q_UNUSED(schema); } + + signals: + void editRequested(); + void cancelRequested(); + void acceptRequested(); + + protected: + virtual bool eventFilter(QObject* watched, QEvent* e); + + /*! Sets \a v as view widget for this editor. The view will be assigned as focus proxy + for the editor, its events will be filtered, it will be resized when neede, and so on. */ + void setViewWidget(QWidget *v); + + /*! Moves child widget within the viewport if the parent is scrollview (otherwise does nothing). + Use this for child widgets that are outside of this editor widget, instead of calling QWidget::move(). */ + void moveChild( QWidget * child, int x, int y ); + + /*! Allows to force redrawing the related cell by the editor itself. Usable when the editor is not + displayed by a QWidget but rather by table view cell itself, for example KexiBlobTableEdit. */ + void repaintRelatedCell(); + + KexiTableViewColumn *m_column; + int m_leftMargin; + int m_rightMargin, m_rightMarginWhenFocused; + QScrollView* m_scrollView; //!< may be 0 if the parent is not a scrollview + bool m_usesSelectedTextColor : 1; //!< set in ctor, @see usesSelectedTextColor() + + private: + QWidget* m_view; +}; + +//! Declaration of cell editor factory +#define KEXI_DECLARE_CELLEDITOR_FACTORY_ITEM(factoryclassname) \ + class factoryclassname : public KexiCellEditorFactoryItem \ + { \ + public: \ + factoryclassname(); \ + virtual ~factoryclassname(); \ + \ + protected: \ + virtual KexiTableEdit* createEditor(KexiTableViewColumn &column, QWidget* parent = 0); \ + }; + +//! Implementation of cell editor factory +#define KEXI_CELLEDITOR_FACTORY_ITEM_IMPL(factoryclassname, itemclassname) \ +factoryclassname::factoryclassname() \ + : KexiCellEditorFactoryItem() \ +{ \ + m_className = "" #itemclassname ""; \ +} \ +\ +factoryclassname::~factoryclassname() \ +{} \ +\ +KexiTableEdit* factoryclassname::createEditor( \ + KexiTableViewColumn &column, QWidget* parent) \ +{ \ + return new itemclassname(column, parent); \ +} + +#endif diff --git a/kexi/widget/tableview/kexitableitem.cpp b/kexi/widget/tableview/kexitableitem.cpp new file mode 100644 index 00000000..e1669a35 --- /dev/null +++ b/kexi/widget/tableview/kexitableitem.cpp @@ -0,0 +1,62 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Lucijan Busch <lucijan@gmx.at> + Copyright (C) 2003 Daniel Molkentin <molkentin@kde.org> + Copyright (C) 2003 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. + + Original Author: Till Busch <till@bux.at> + Original Project: buX (www.bux.at) +*/ + +#include "kexitableitem.h" + +#include <kdebug.h> + +KexiTableItem::KexiTableItem(int numCols) +: KexiTableItemBase(numCols) +{ +//js m_userData=0; +//js m_columns.resize(numCols); +//js m_insertItem = false; +//js m_pTable = 0; +} + +KexiTableItem::~KexiTableItem() +{ +} + +void +KexiTableItem::init(int numCols) +{ + clear(); + resize(numCols); +} + +void +KexiTableItem::clearValues() +{ + init(count()); +} + +void +KexiTableItem::debug() const +{ + QString s = QString("KexiTableItem (%1 items)").arg(size()); + for (uint i = 0; i < size(); i++) + s.append( QString::number(i)+":"+at(i).toString()+" " ); + kexidbg << s << endl; +} diff --git a/kexi/widget/tableview/kexitableitem.h b/kexi/widget/tableview/kexitableitem.h new file mode 100644 index 00000000..00074e82 --- /dev/null +++ b/kexi/widget/tableview/kexitableitem.h @@ -0,0 +1,58 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Lucijan Busch <lucijan@gmx.at> + Copyright (C) 2003 Daniel Molkentin <molkentin@kde.org> + Copyright (C) 2003 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. + + Original Author: Till Busch <till@bux.at> + Original Project: buX (www.bux.at) +*/ + +#ifndef KEXITABLEITEM_H +#define KEXITABLEITEM_H + +#include <qstring.h> +#include <qdatetime.h> +#include <qvaluevector.h> +#include <qvariant.h> + +#include <kexidb/connection.h> + +typedef KexiDB::RowData KexiTableItemBase; + +class KEXIDATATABLE_EXPORT KexiTableItem : public KexiTableItemBase +{ + public: + ~KexiTableItem(); + + /*! Clears existing column values and inits new \a numCols + columns with empty values. ist of values is resized to \a numCols. */ + void init(int numCols); + + /*! Clears existing column values, current number of columns is preserved. */ + void clearValues(); + + /*! Prints debug string for this item. */ + void debug() const; + + protected: + KexiTableItem(int numCols); + + friend class KexiTableViewData; +}; + +#endif diff --git a/kexi/widget/tableview/kexitableview.cpp b/kexi/widget/tableview/kexitableview.cpp new file mode 100644 index 00000000..108b1696 --- /dev/null +++ b/kexi/widget/tableview/kexitableview.cpp @@ -0,0 +1,2607 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Till Busch <till@bux.at> + Copyright (C) 2003 Lucijan Busch <lucijan@gmx.at> + Copyright (C) 2003 Daniel Molkentin <molkentin@kde.org> + Copyright (C) 2003 Joseph Wenninger <jowenn@kde.org> + Copyright (C) 2003-2006 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. + + Original Author: Till Busch <till@bux.at> + Original Project: buX (www.bux.at) +*/ + +#include <qpainter.h> +#include <qkeycode.h> +#include <qlineedit.h> +#include <qcombobox.h> +#include <qwmatrix.h> +#include <qtimer.h> +#include <qpopupmenu.h> +#include <qcursor.h> +#include <qstyle.h> +#include <qlayout.h> +#include <qlabel.h> +#include <qwhatsthis.h> + +#include <kglobal.h> +#include <klocale.h> +#include <kdebug.h> +#include <kapplication.h> +#include <kiconloader.h> +#include <kmessagebox.h> + +#ifndef KEXI_NO_PRINT +# include <kprinter.h> +#endif + +#include "kexitableview.h" +#include <kexiutils/utils.h> +#include <kexiutils/validator.h> + +#include "kexicelleditorfactory.h" +#include "kexitableviewheader.h" +#include "kexitableview_p.h" +#include <widget/utils/kexirecordmarker.h> +#include <widget/utils/kexidisplayutils.h> +#include <kexidb/cursor.h> + +KexiTableView::Appearance::Appearance(QWidget *widget) + : alternateBackgroundColor( KGlobalSettings::alternateBackgroundColor() ) +{ + //set defaults + if (qApp) { + QPalette p = widget ? widget->palette() : qApp->palette(); + baseColor = p.active().base(); + textColor = p.active().text(); + borderColor = QColor(200,200,200); + emptyAreaColor = p.active().color(QColorGroup::Base); + rowHighlightingColor = KexiUtils::blendedColors(p.active().highlight(), baseColor, 33, 66); + rowMouseOverHighlightingColor = KexiUtils::blendedColors(p.active().highlight(), baseColor, 10, 90); + rowMouseOverAlternateHighlightingColor = KexiUtils::blendedColors(p.active().highlight(), alternateBackgroundColor, 10, 90); + rowHighlightingTextColor = textColor; + rowMouseOverHighlightingTextColor = textColor; + } + backgroundAltering = true; + rowMouseOverHighlightingEnabled = true; + rowHighlightingEnabled = true; + persistentSelections = true; + navigatorEnabled = true; + fullRowSelection = false; + gridEnabled = true; +} + +//----------------------------------------- + +//! @internal A special What's This class displaying information about a given column +class KexiTableView::WhatsThis : public QWhatsThis +{ + public: + WhatsThis(KexiTableView* tv) : QWhatsThis(tv), m_tv(tv) + { + Q_ASSERT(tv); + } + virtual ~WhatsThis() + { + } + virtual QString text( const QPoint & pos) + { + const int leftMargin = m_tv->verticalHeaderVisible() ? m_tv->verticalHeader()->width() : 0; + //const int topMargin = m_tv->horizontalHeaderVisible() ? m_tv->d->pTopHeader->height() : 0; + //const int bottomMargin = m_tv->d->appearance.navigatorEnabled ? m_tv->m_navPanel->height() : 0; + if (KexiUtils::hasParent(m_tv->verticalHeader(), m_tv->childAt(pos))) { + return i18n("Contains a pointer to the currently selected row"); + } + else if (KexiUtils::hasParent(m_tv->m_navPanel, m_tv->childAt(pos))) { + return i18n("Row navigator"); +// return QWhatsThis::textFor(m_tv->m_navPanel, QPoint( pos.x(), pos.y() - m_tv->height() + bottomMargin )); + } + KexiDB::Field *f = m_tv->field( m_tv->columnAt(pos.x()-leftMargin) ); + if (!f) + return QString::null; + return f->description().isEmpty() ? f->captionOrName() : f->description(); + } + protected: + KexiTableView *m_tv; +}; + +//----------------------------------------- + +KexiTableViewCellToolTip::KexiTableViewCellToolTip( KexiTableView * tableView ) + : QToolTip(tableView->viewport()) + , m_tableView(tableView) +{ +} + +KexiTableViewCellToolTip::~KexiTableViewCellToolTip() +{ + remove(parentWidget()); +} + +void KexiTableViewCellToolTip::maybeTip( const QPoint & p ) +{ + const QPoint cp( m_tableView->viewportToContents( p ) ); + const int row = m_tableView->rowAt( cp.y(), true/*ignoreEnd*/ ); + const int col = m_tableView->columnAt( cp.x() ); + + //show tooltip if needed + if (col>=0 && row>=0) { + KexiTableEdit *editor = m_tableView->tableEditorWidget( col ); + const bool insertRowSelected = m_tableView->isInsertingEnabled() && row==m_tableView->rows(); + KexiTableItem *item = insertRowSelected ? m_tableView->m_insertItem : m_tableView->itemAt( row ); + if (editor && item && (col < (int)item->count())) { + int w = m_tableView->columnWidth( col ); + int h = m_tableView->rowHeight(); + int x = 0; + int y_offset = 0; + int align = Qt::SingleLine | Qt::AlignVCenter; + QString txtValue; + QVariant cellValue; + KexiTableViewColumn *tvcol = m_tableView->column(col); + if (!m_tableView->getVisibleLookupValue(cellValue, editor, item, tvcol)) + cellValue = insertRowSelected ? editor->displayedField()->defaultValue() : item->at(col); //display default value if available + const bool focused = m_tableView->selectedItem() == item && col == m_tableView->currentColumn(); + editor->setupContents( 0, focused, cellValue, txtValue, align, x, y_offset, w, h ); + QRect realRect(m_tableView->columnPos(col)-m_tableView->contentsX(), + m_tableView->rowPos(row)-m_tableView->contentsY(), w, h); //m_tableView->cellGeometry( row, col )); + if (editor->showToolTipIfNeeded( + txtValue.isEmpty() ? item->at(col) : QVariant(txtValue), + realRect, m_tableView->fontMetrics(), focused)) + { + QString squeezedTxtValue; + if (txtValue.length() > 50) + squeezedTxtValue = txtValue.left(100) + "..."; + else + squeezedTxtValue = txtValue; + tip( realRect, squeezedTxtValue ); + } + } + } +} + +//----------------------------------------- + +KexiTableView::KexiTableView(KexiTableViewData* data, QWidget* parent, const char* name) +: QScrollView(parent, name, /*Qt::WRepaintNoErase | */Qt::WStaticContents /*| Qt::WResizeNoErase*/) +, KexiRecordNavigatorHandler() +, KexiSharedActionClient() +, KexiDataAwareObjectInterface() +{ +//not needed KexiTableView::initCellEditorFactories(); + + d = new KexiTableViewPrivate(this); + + connect( kapp, SIGNAL( settingsChanged(int) ), SLOT( slotSettingsChanged(int) ) ); + slotSettingsChanged(KApplication::SETTINGS_SHORTCUTS); + + m_data = new KexiTableViewData(); //to prevent crash because m_data==0 + m_owner = true; //-this will be deleted if needed + + setResizePolicy(Manual); + viewport()->setBackgroundMode(Qt::NoBackground); +// viewport()->setFocusPolicy(StrongFocus); + viewport()->setFocusPolicy(WheelFocus); + setFocusPolicy(WheelFocus); //<--- !!!!! important (was NoFocus), + // otherwise QApplication::setActiveWindow() won't activate + // this widget when needed! +// setFocusProxy(viewport()); + viewport()->installEventFilter(this); + + //setup colors defaults + setBackgroundMode(Qt::PaletteBackground); +// setEmptyAreaColor(d->appearance.baseColor);//palette().active().color(QColorGroup::Base)); + +// d->baseColor = colorGroup().base(); +// d->textColor = colorGroup().text(); + +// d->altColor = KGlobalSettings::alternateBackgroundColor(); +// d->grayColor = QColor(200,200,200); + d->diagonalGrayPattern = QBrush(d->appearance.borderColor, Qt::BDiagPattern); + + setLineWidth(1); + horizontalScrollBar()->installEventFilter(this); + horizontalScrollBar()->raise(); + verticalScrollBar()->raise(); + + //context menu + m_popupMenu = new KPopupMenu(this, "contextMenu"); +#if 0 //moved to mainwindow's actions + d->menu_id_addRecord = m_popupMenu->insertItem(i18n("Add Record"), this, SLOT(addRecord()), Qt::CTRL+Qt::Key_Insert); + d->menu_id_removeRecord = m_popupMenu->insertItem( + kapp->iconLoader()->loadIcon("button_cancel", KIcon::Small), + i18n("Remove Record"), this, SLOT(removeRecord()), Qt::CTRL+Qt::Key_Delete); +#endif + +#ifdef Q_WS_WIN + d->rowHeight = fontMetrics().lineSpacing() + 4; +#else + d->rowHeight = fontMetrics().lineSpacing() + 1; +#endif + + if(d->rowHeight < 17) + d->rowHeight = 17; + + d->pUpdateTimer = new QTimer(this); + +// setMargins(14, fontMetrics().height() + 4, 0, 0); + + // Create headers + m_horizontalHeader = new KexiTableViewHeader(this, "topHeader"); + m_horizontalHeader->setSelectionBackgroundColor( palette().active().highlight() ); + m_horizontalHeader->setOrientation(Qt::Horizontal); + m_horizontalHeader->setTracking(false); + m_horizontalHeader->setMovingEnabled(false); + connect(m_horizontalHeader, SIGNAL(sizeChange(int,int,int)), this, SLOT(slotTopHeaderSizeChange(int,int,int))); + + m_verticalHeader = new KexiRecordMarker(this, "rm"); + m_verticalHeader->setSelectionBackgroundColor( palette().active().highlight() ); + m_verticalHeader->setCellHeight(d->rowHeight); +// m_verticalHeader->setFixedWidth(d->rowHeight); + m_verticalHeader->setCurrentRow(-1); + + setMargins( + QMIN(m_horizontalHeader->sizeHint().height(), d->rowHeight), + m_horizontalHeader->sizeHint().height(), 0, 0); + + setupNavigator(); + +// setMinimumHeight(horizontalScrollBar()->height() + d->rowHeight + topMargin()); + +// navPanelLyr->addStretch(25); +// enableClipper(true); + + if (data) + setData( data ); + +#if 0//(js) doesn't work! + d->scrollTimer = new QTimer(this); + connect(d->scrollTimer, SIGNAL(timeout()), this, SLOT(slotAutoScroll())); +#endif + +// setBackgroundAltering(true); +// setFullRowSelectionEnabled(false); + + setAcceptDrops(true); + viewport()->setAcceptDrops(true); + + // Connect header, table and scrollbars + connect(horizontalScrollBar(), SIGNAL(valueChanged(int)), m_horizontalHeader, SLOT(setOffset(int))); + connect(verticalScrollBar(), SIGNAL(valueChanged(int)), m_verticalHeader, SLOT(setOffset(int))); + connect(m_horizontalHeader, SIGNAL(sizeChange(int, int, int)), this, SLOT(slotColumnWidthChanged(int, int, int))); + connect(m_horizontalHeader, SIGNAL(sectionHandleDoubleClicked(int)), this, SLOT(slotSectionHandleDoubleClicked(int))); + connect(m_horizontalHeader, SIGNAL(clicked(int)), this, SLOT(sortColumnInternal(int))); + + connect(d->pUpdateTimer, SIGNAL(timeout()), this, SLOT(slotUpdate())); + +// horizontalScrollBar()->show(); + updateScrollBars(); +// resize(sizeHint()); +// updateContents(); +// setMinimumHeight(horizontalScrollBar()->height() + d->rowHeight + topMargin()); + +//TMP +//setVerticalHeaderVisible(false); +//setHorizontalHeaderVisible(false); + +//will be updated by setAppearance: updateFonts(); + setAppearance(d->appearance); //refresh + + d->cellToolTip = new KexiTableViewCellToolTip(this); + new WhatsThis(this); +} + +KexiTableView::~KexiTableView() +{ + cancelRowEdit(); + + KexiTableViewData *data = m_data; + m_data = 0; + if (m_owner) { + if (data) + data->deleteLater(); + } + delete d; +} + +void KexiTableView::clearVariables() +{ + KexiDataAwareObjectInterface::clearVariables(); + d->clearVariables(); +} + +/*void KexiTableView::initActions(KActionCollection *ac) +{ + emit reloadActions(ac); +}*/ + +void KexiTableView::setupNavigator() +{ + updateScrollBars(); + + m_navPanel = new KexiRecordNavigator(this, leftMargin(), "navPanel"); + m_navPanel->setRecordHandler(this); + m_navPanel->setSizePolicy(QSizePolicy::Minimum,QSizePolicy::Preferred); +} + +void KexiTableView::initDataContents() +{ + updateWidgetContentsSize(); + + KexiDataAwareObjectInterface::initDataContents(); + + m_navPanel->showEditingIndicator(false); +} + +void KexiTableView::addHeaderColumn(const QString& caption, const QString& description, + const QIconSet& icon, int width) +{ + const int nr = m_horizontalHeader->count(); + if (icon.isNull()) + m_horizontalHeader->addLabel(caption, width); + else + m_horizontalHeader->addLabel(icon, caption, width); + + if (!description.isEmpty()) + m_horizontalHeader->setToolTip(nr, description); +} + +void KexiTableView::updateWidgetContentsSize() +{ + QSize s(tableSize()); + resizeContents(s.width(), s.height()); +} + +void KexiTableView::slotRowsDeleted( const QValueList<int> &rows ) +{ + viewport()->repaint(); + updateWidgetContentsSize(); + setCursorPosition(QMAX(0, (int)m_curRow - (int)rows.count()), -1, true); +} + + +/*void KexiTableView::addDropFilter(const QString &filter) +{ + d->dropFilters.append(filter); + viewport()->setAcceptDrops(true); +}*/ + +void KexiTableView::setFont( const QFont &font ) +{ + QScrollView::setFont(font); + updateFonts(true); +} + +void KexiTableView::updateFonts(bool repaint) +{ +#ifdef Q_WS_WIN + d->rowHeight = fontMetrics().lineSpacing() + 4; +#else + d->rowHeight = fontMetrics().lineSpacing() + 1; +#endif + if (d->appearance.fullRowSelection) { + d->rowHeight -= 1; + } + if(d->rowHeight < 17) + d->rowHeight = 17; +// if(d->rowHeight < 22) +// d->rowHeight = 22; + setMargins( + QMIN(m_horizontalHeader->sizeHint().height(), d->rowHeight), + m_horizontalHeader->sizeHint().height(), 0, 0); +// setMargins(14, d->rowHeight, 0, 0); + m_verticalHeader->setCellHeight(d->rowHeight); + + KexiDisplayUtils::initDisplayForAutonumberSign(d->autonumberSignDisplayParameters, this); + KexiDisplayUtils::initDisplayForDefaultValue(d->defaultValueDisplayParameters, this); + + if (repaint) + updateContents(); +} + +void KexiTableView::updateAllVisibleRowsBelow(int row) +{ + //get last visible row + int r = rowAt(clipper()->height()+contentsY()); + if (r==-1) { + r = rows()+1+(isInsertingEnabled()?1:0); + } + //update all visible rows below + int leftcol = m_horizontalHeader->sectionAt( m_horizontalHeader->offset() ); +// int row = m_curRow; + updateContents( columnPos( leftcol ), rowPos(row), + clipper()->width(), clipper()->height() - (rowPos(row) - contentsY()) ); +} + +void KexiTableView::clearColumnsInternal(bool /*repaint*/) +{ + while(m_horizontalHeader->count()>0) + m_horizontalHeader->removeLabel(0); +} + +void KexiTableView::slotUpdate() +{ +// kdDebug(44021) << " KexiTableView::slotUpdate() -- " << endl; +// QSize s(tableSize()); +// viewport()->setUpdatesEnabled(false); +/// resizeContents(s.width(), s.height()); +// viewport()->setUpdatesEnabled(true); + + updateContents(); + updateScrollBars(); + if (m_navPanel) + m_navPanel->updateGeometry(leftMargin()); +// updateNavPanelGeometry(); + + updateWidgetContentsSize(); +// updateContents(0, contentsY()+clipper()->height()-2*d->rowHeight, clipper()->width(), d->rowHeight*3); + + //updateGeometries(); +// updateContents(0, 0, viewport()->width(), contentsHeight()); +// updateGeometries(); +} + +int KexiTableView::currentLocalSortingOrder() const +{ + if (m_horizontalHeader->sortIndicatorSection()==-1) + return 0; + return (m_horizontalHeader->sortIndicatorOrder() == Qt::Ascending) ? 1 : -1; +} + +void KexiTableView::setLocalSortingOrder(int col, int order) +{ + if (order == 0) + col = -1; + if (col>=0) + m_horizontalHeader->setSortIndicator(col, (order==1) ? Qt::Ascending : Qt::Descending); +} + +int KexiTableView::currentLocalSortColumn() const +{ + return m_horizontalHeader->sortIndicatorSection(); +} + +void KexiTableView::updateGUIAfterSorting() +{ + int cw = columnWidth(m_curCol); + int rh = rowHeight(); + +// m_verticalHeader->setCurrentRow(m_curRow); + center(columnPos(m_curCol) + cw / 2, rowPos(m_curRow) + rh / 2); +// updateCell(oldRow, m_curCol); +// updateCell(m_curRow, m_curCol); +// slotUpdate(); + + updateContents(); +// d->pUpdateTimer->start(1,true); +} + +QSizePolicy KexiTableView::sizePolicy() const +{ + // this widget is expandable + return QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); +} + +QSize KexiTableView::sizeHint() const +{ + const QSize &ts = tableSize(); + int w = QMAX( ts.width() + leftMargin()+ verticalScrollBar()->sizeHint().width() + 2*2, + (m_navPanel->isVisible() ? m_navPanel->width() : 0) ); + int h = QMAX( ts.height()+topMargin()+horizontalScrollBar()->sizeHint().height(), + minimumSizeHint().height() ); + w = QMIN( w, qApp->desktop()->width()*3/4 ); //stretch + h = QMIN( h, qApp->desktop()->height()*3/4 ); //stretch + +// kexidbg << "KexiTableView::sizeHint()= " <<w <<", " <<h << endl; + + return QSize(w, h); + /*QSize( + QMAX( ts.width() + leftMargin() + 2*2, (m_navPanel ? m_navPanel->width() : 0) ), + //+ QMIN(m_verticalHeader->width(),d->rowHeight) + margin()*2, + QMAX( ts.height()+topMargin()+horizontalScrollBar()->sizeHint().height(), + minimumSizeHint().height() ) + );*/ +// QMAX(ts.height() + topMargin(), minimumSizeHint().height()) ); +} + +QSize KexiTableView::minimumSizeHint() const +{ + return QSize( + leftMargin() + ((columns()>0)?columnWidth(0):KEXI_DEFAULT_DATA_COLUMN_WIDTH) + 2*2, + d->rowHeight*5/2 + topMargin() + (m_navPanel && m_navPanel->isVisible() ? m_navPanel->height() : 0) + ); +} + +void KexiTableView::createBuffer(int width, int height) +{ + if(!d->pBufferPm) + d->pBufferPm = new QPixmap(width, height); + else + if(d->pBufferPm->width() < width || d->pBufferPm->height() < height) + d->pBufferPm->resize(width, height); +// d->pBufferPm->fill(); +} + +//internal +inline void KexiTableView::paintRow(KexiTableItem *item, + QPainter *pb, int r, int rowp, int cx, int cy, + int colfirst, int collast, int maxwc) +{ + if (!item) + return; + // Go through the columns in the row r + // if we know from where to where, go through [colfirst, collast], + // else go through all of them + if (colfirst==-1) + colfirst=0; + if (collast==-1) + collast=columns()-1; + + int transly = rowp-cy; + + if (d->appearance.rowHighlightingEnabled && r == m_curRow && !d->appearance.fullRowSelection) { + pb->fillRect(0, transly, maxwc, d->rowHeight, d->appearance.rowHighlightingColor); + } + else if (d->appearance.rowMouseOverHighlightingEnabled && r == d->highlightedRow) { + if(d->appearance.backgroundAltering && (r%2 != 0)) + pb->fillRect(0, transly, maxwc, d->rowHeight, d->appearance.rowMouseOverAlternateHighlightingColor); + else + pb->fillRect(0, transly, maxwc, d->rowHeight, d->appearance.rowMouseOverHighlightingColor); + } + else { + if(d->appearance.backgroundAltering && (r%2 != 0)) + pb->fillRect(0, transly, maxwc, d->rowHeight, d->appearance.alternateBackgroundColor); + else + pb->fillRect(0, transly, maxwc, d->rowHeight, d->appearance.baseColor); + } + + for(int c = colfirst; c <= collast; c++) + { + // get position and width of column c + int colp = columnPos(c); + if (colp==-1) + continue; //invisible column? + int colw = columnWidth(c); + int translx = colp-cx; + + // Translate painter and draw the cell + pb->saveWorldMatrix(); + pb->translate(translx, transly); + paintCell( pb, item, c, r, QRect(colp, rowp, colw, d->rowHeight)); + pb->restoreWorldMatrix(); + } + + if (m_dragIndicatorLine>=0) { + int y_line = -1; + if (r==(rows()-1) && m_dragIndicatorLine==rows()) { + y_line = transly+d->rowHeight-3; //draw at last line + } + if (m_dragIndicatorLine==r) { + y_line = transly+1; + } + if (y_line>=0) { + RasterOp op = pb->rasterOp(); + pb->setRasterOp(XorROP); + pb->setPen( QPen(Qt::white, 3) ); + pb->drawLine(0, y_line, maxwc, y_line); + pb->setRasterOp(op); + } + } +} + +void KexiTableView::drawContents( QPainter *p, int cx, int cy, int cw, int ch) +{ + if (d->disableDrawContents) + return; + int colfirst = columnAt(cx); + int rowfirst = rowAt(cy); + int collast = columnAt(cx + cw-1); + int rowlast = rowAt(cy + ch-1); + bool inserting = isInsertingEnabled(); + bool plus1row = false; //true if we should show 'inserting' row at the end + bool paintOnlyInsertRow = false; + +/* kdDebug(44021) << QString(" KexiTableView::drawContents(cx:%1 cy:%2 cw:%3 ch:%4)") + .arg(cx).arg(cy).arg(cw).arg(ch) << endl;*/ + + if (rowlast == -1) { + rowlast = rows() - 1; + plus1row = inserting; + if (rowfirst == -1) { + if (rowAt(cy - d->rowHeight) != -1) { + paintOnlyInsertRow = true; +// kdDebug(44021) << "-- paintOnlyInsertRow --" << endl; + } + } + } +// kdDebug(44021) << "rowfirst="<<rowfirst<<" rowlast="<<rowlast<<" rows()="<<rows()<<endl; +// kdDebug(44021)<<" plus1row=" << plus1row<<endl; + + if ( collast == -1 ) + collast = columns() - 1; + + if (colfirst>collast) { + int tmp = colfirst; + colfirst = collast; + collast = tmp; + } + if (rowfirst>rowlast) { + int tmp = rowfirst; + rowfirst = rowlast; + rowlast = tmp; + } + +// qDebug("cx:%3d cy:%3d w:%3d h:%3d col:%2d..%2d row:%2d..%2d tsize:%4d,%4d", +// cx, cy, cw, ch, colfirst, collast, rowfirst, rowlast, tableSize().width(), tableSize().height()); +// triggerUpdate(); + + if (rowfirst == -1 || colfirst == -1) { + if (!paintOnlyInsertRow && !plus1row) { + paintEmptyArea(p, cx, cy, cw, ch); + return; + } + } + + createBuffer(cw, ch); + if(d->pBufferPm->isNull()) + return; + QPainter *pb = new QPainter(d->pBufferPm, this); +// pb->fillRect(0, 0, cw, ch, colorGroup().base()); + +// int maxwc = QMIN(cw, (columnPos(d->numCols - 1) + columnWidth(d->numCols - 1))); + int maxwc = columnPos(columns() - 1) + columnWidth(columns() - 1); +// kdDebug(44021) << "KexiTableView::drawContents(): maxwc: " << maxwc << endl; + + pb->fillRect(cx, cy, cw, ch, d->appearance.baseColor); + + int rowp; + int r; + if (paintOnlyInsertRow) { + r = rows(); + rowp = rowPos(r); // 'insert' row's position + } + else { + QPtrListIterator<KexiTableItem> it = m_data->iterator(); + it += rowfirst;//move to 1st row + rowp = rowPos(rowfirst); // row position + for (r = rowfirst;r <= rowlast; r++, ++it, rowp+=d->rowHeight) { + paintRow(it.current(), pb, r, rowp, cx, cy, colfirst, collast, maxwc); + } + } + + if (plus1row) { //additional - 'insert' row + paintRow(m_insertItem, pb, r, rowp, cx, cy, colfirst, collast, maxwc); + } + + delete pb; + + p->drawPixmap(cx,cy,*d->pBufferPm, 0,0,cw,ch); + + //(js) + paintEmptyArea(p, cx, cy, cw, ch); +} + +bool KexiTableView::isDefaultValueDisplayed(KexiTableItem *item, int col, QVariant* value) +{ + const bool cursorAtInsertRowOrEditingNewRow = (item == m_insertItem || (m_newRowEditing && m_currentItem == item)); + KexiTableViewColumn *tvcol; + if (cursorAtInsertRowOrEditingNewRow + && (tvcol = m_data->column(col)) + && hasDefaultValueAt(*tvcol) + && !tvcol->field()->isAutoIncrement()) + { + if (value) + *value = tvcol->field()->defaultValue(); + return true; + } + return false; +} + +void KexiTableView::paintCell(QPainter* p, KexiTableItem *item, int col, int row, const QRect &cr, bool print) +{ + p->save(); + Q_UNUSED(print); + int w = cr.width(); + int h = cr.height(); + int x2 = w - 1; + int y2 = h - 1; + +/* if (0==qstrcmp("KexiComboBoxPopup",parentWidget()->className())) { + kexidbg << parentWidget()->className() << " >>>>>> KexiTableView::paintCell(col=" << col <<"row="<<row<<") w="<<w<<endl; + }*/ + + // Draw our lines + QPen pen(p->pen()); + + if (d->appearance.gridEnabled) { + p->setPen(d->appearance.borderColor); + p->drawLine( x2, 0, x2, y2 ); // right + p->drawLine( 0, y2, x2, y2 ); // bottom + } + p->setPen(pen); + + if (m_editor && row == m_curRow && col == m_curCol //don't paint contents of edited cell + && m_editor->hasFocusableWidget() //..if it's visible + ) { + p->restore(); + return; + } + + KexiTableEdit *edit = tableEditorWidget( col, /*ignoreMissingEditor=*/true ); +// if (!edit) +// return; + + int x = edit ? edit->leftMargin() : 0; + int y_offset=0; + + int align = Qt::SingleLine | Qt::AlignVCenter; + QString txt; //text to draw + + KexiTableViewColumn *tvcol = m_data->column(col); + + QVariant cellValue; + if (col < (int)item->count()) { + if (m_currentItem == item) { + if (m_editor && row == m_curRow && col == m_curCol + && !m_editor->hasFocusableWidget()) + { + //we're over editing cell and the editor has no widget + // - we're displaying internal values, not buffered +// bool ok; + cellValue = m_editor->value(); + } + else { + //we're displaying values from edit buffer, if available + // this assignment will also get default value if there's no actual value set + cellValue = *bufferedValueAt(col); + } + } + else { + cellValue = item->at(col); + } + } + + bool defaultValueDisplayed = isDefaultValueDisplayed(item, col); + + if ((item == m_insertItem /*|| m_newRowEditing*/) && cellValue.isNull()) { + if (!tvcol->field()->isAutoIncrement() && !tvcol->field()->defaultValue().isNull()) { + //display default value in the "insert row", if available + //(but not if there is autoincrement flag set) + cellValue = tvcol->field()->defaultValue(); + defaultValueDisplayed = true; + } + } + + const bool columnReadOnly = tvcol->isReadOnly(); + const bool dontPaintNonpersistentSelectionBecauseDifferentRowHasBeenHighlighted + = d->appearance.rowHighlightingEnabled && !d->appearance.persistentSelections + && m_curRow /*d->highlightedRow*/ >= 0 && row != m_curRow; //d->highlightedRow; + + // setup default pen + QPen defaultPen; + const bool usesSelectedTextColor = edit && edit->usesSelectedTextColor(); + if (defaultValueDisplayed) { + if (col == m_curCol && row == m_curRow && usesSelectedTextColor) + defaultPen = d->defaultValueDisplayParameters.selectedTextColor; + else + defaultPen = d->defaultValueDisplayParameters.textColor; + } + else if (d->appearance.fullRowSelection + && (row == d->highlightedRow || (row == m_curRow && d->highlightedRow==-1)) + && usesSelectedTextColor ) + { + defaultPen = d->appearance.rowHighlightingTextColor; //special case: highlighted row + } + else if (d->appearance.fullRowSelection && row == m_curRow && usesSelectedTextColor) + { + defaultPen = d->appearance.textColor; //special case for full row selection + } + else if (m_currentItem == item && col == m_curCol && !columnReadOnly + && !dontPaintNonpersistentSelectionBecauseDifferentRowHasBeenHighlighted + && usesSelectedTextColor) + { + defaultPen = colorGroup().highlightedText(); //selected text + } + else if (d->appearance.rowHighlightingEnabled && row == m_curRow + && !dontPaintNonpersistentSelectionBecauseDifferentRowHasBeenHighlighted + && usesSelectedTextColor) + { + defaultPen = d->appearance.rowHighlightingTextColor; + } + else if (d->appearance.rowMouseOverHighlightingEnabled && row == d->highlightedRow + && !dontPaintNonpersistentSelectionBecauseDifferentRowHasBeenHighlighted + && usesSelectedTextColor) + { + defaultPen = d->appearance.rowMouseOverHighlightingTextColor; + } + else + defaultPen = d->appearance.textColor; + + if (edit) { + if (defaultValueDisplayed) + p->setFont( d->defaultValueDisplayParameters.font ); + p->setPen( defaultPen ); + + //get visible lookup value if available + getVisibleLookupValue(cellValue, edit, item, tvcol); + + edit->setupContents( p, m_currentItem == item && col == m_curCol, + cellValue, txt, align, x, y_offset, w, h ); + } + if (!d->appearance.gridEnabled) + y_offset++; //correction because we're not drawing cell borders + + if (d->appearance.fullRowSelection && d->appearance.fullRowSelection) { +// p->fillRect(x, y_offset, x+w-1, y_offset+h-1, red); + } + if (m_currentItem == item && (col == m_curCol || d->appearance.fullRowSelection)) { + if (edit && ((d->appearance.rowHighlightingEnabled && !d->appearance.fullRowSelection) || (row == m_curRow && d->highlightedRow==-1 && d->appearance.fullRowSelection))) //!dontPaintNonpersistentSelectionBecauseDifferentRowHasBeenHighlighted) + edit->paintSelectionBackground( p, isEnabled(), txt, align, x, y_offset, w, h, + isEnabled() ? colorGroup().highlight() : QColor(200,200,200),//d->grayColor, + p->fontMetrics(), columnReadOnly, d->appearance.fullRowSelection ); + } + + if (!edit) { + p->fillRect(0, 0, x2, y2, d->diagonalGrayPattern); + } + +// If we are in the focus cell, draw indication + if(m_currentItem == item && col == m_curCol //js: && !d->recordIndicator) + && !d->appearance.fullRowSelection) + { +// kexidbg << ">>> CURRENT CELL ("<<m_curCol<<"," << m_curRow<<") focus="<<has_focus<<endl; +// if (has_focus) { + if (isEnabled()) { + p->setPen(d->appearance.textColor); + } + else { + QPen gray_pen(p->pen()); + gray_pen.setColor(d->appearance.borderColor); + p->setPen(gray_pen); + } + if (edit) + edit->paintFocusBorders( p, cellValue, 0, 0, x2, y2 ); + else + p->drawRect(0, 0, x2, y2); + } + +/// bool autonumber = false; + if ((!m_newRowEditing && item == m_insertItem) + || (m_newRowEditing && item == m_currentItem && cellValue.isNull())) { + //we're in "insert row" + if (tvcol->field()->isAutoIncrement()) { + //"autonumber" column +// txt = i18n("(autonumber)"); +// autonumber = true; +// if (autonumber) { + KexiDisplayUtils::paintAutonumberSign(d->autonumberSignDisplayParameters, p, + x, y_offset, w - x - x - ((align & Qt::AlignLeft)?2:0), h, align); +// } + } + } + + // draw text + if (!txt.isEmpty()) { + if (defaultValueDisplayed) + p->setFont( d->defaultValueDisplayParameters.font ); + p->setPen( defaultPen ); + p->drawText(x, y_offset, w - (x + x)- ((align & Qt::AlignLeft)?2:0)/*right space*/, h, + align, txt); + } + p->restore(); +} + +QPoint KexiTableView::contentsToViewport2( const QPoint &p ) +{ + return QPoint( p.x() - contentsX(), p.y() - contentsY() ); +} + +void KexiTableView::contentsToViewport2( int x, int y, int& vx, int& vy ) +{ + const QPoint v = contentsToViewport2( QPoint( x, y ) ); + vx = v.x(); + vy = v.y(); +} + +QPoint KexiTableView::viewportToContents2( const QPoint& vp ) +{ + return QPoint( vp.x() + contentsX(), + vp.y() + contentsY() ); +} + +void KexiTableView::paintEmptyArea( QPainter *p, int cx, int cy, int cw, int ch ) +{ +// qDebug("%s: paintEmptyArea(x:%d y:%d w:%d h:%d)", (const char*)parentWidget()->caption(),cx,cy,cw,ch); + + // Regions work with shorts, so avoid an overflow and adjust the + // table size to the visible size + QSize ts( tableSize() ); +// ts.setWidth( QMIN( ts.width(), visibleWidth() ) ); +// ts.setHeight( QMIN( ts.height() - (m_navPanel ? m_navPanel->height() : 0), visibleHeight()) ); +/* kdDebug(44021) << QString(" (cx:%1 cy:%2 cw:%3 ch:%4)") + .arg(cx).arg(cy).arg(cw).arg(ch) << endl; + kdDebug(44021) << QString(" (w:%3 h:%4)") + .arg(ts.width()).arg(ts.height()) << endl;*/ + + // Region of the rect we should draw, calculated in viewport + // coordinates, as a region can't handle bigger coordinates + contentsToViewport2( cx, cy, cx, cy ); + QRegion reg( QRect( cx, cy, cw, ch ) ); + +//kexidbg << "---cy-- " << contentsY() << endl; + + // Subtract the table from it +// reg = reg.subtract( QRect( QPoint( 0, 0 ), ts-QSize(0,m_navPanel->isVisible() ? m_navPanel->height() : 0) ) ); + reg = reg.subtract( QRect( QPoint( 0, 0 ), ts + -QSize(0,QMAX((m_navPanel ? m_navPanel->height() : 0), horizontalScrollBar()->sizeHint().height()) + - (horizontalScrollBar()->isVisible() ? horizontalScrollBar()->sizeHint().height()/2 : 0) + + (horizontalScrollBar()->isVisible() ? 0 : + d->internal_bottomMargin +// horizontalScrollBar()->sizeHint().height()/2 + ) +//- /*d->bottomMargin */ horizontalScrollBar()->sizeHint().height()*3/2 + + contentsY() +// - (verticalScrollBar()->isVisible() ? horizontalScrollBar()->sizeHint().height()/2 : 0) + ) + ) ); +// reg = reg.subtract( QRect( QPoint( 0, 0 ), ts ) ); + + // And draw the rectangles (transformed inc contents coordinates as needed) + QMemArray<QRect> r = reg.rects(); + for ( int i = 0; i < (int)r.count(); i++ ) { + QRect rect( viewportToContents2(r[i].topLeft()), r[i].size() ); +/* kdDebug(44021) << QString("- pEA: p->fillRect(x:%1 y:%2 w:%3 h:%4)") + .arg(rect.x()).arg(rect.y()) + .arg(rect.width()).arg(rect.height()) << endl;*/ +// p->fillRect( QRect(viewportToContents2(r[i].topLeft()),r[i].size()), d->emptyAreaColor ); + p->fillRect( rect, d->appearance.emptyAreaColor ); +// p->fillRect( QRect(viewportToContents2(r[i].topLeft()),r[i].size()), viewport()->backgroundBrush() ); + } +} + +void KexiTableView::contentsMouseDoubleClickEvent(QMouseEvent *e) +{ +// kdDebug(44021) << "KexiTableView::contentsMouseDoubleClickEvent()" << endl; + m_contentsMousePressEvent_dblClick = true; + contentsMousePressEvent(e); + m_contentsMousePressEvent_dblClick = false; + + if(m_currentItem) + { + if(d->editOnDoubleClick && columnEditable(m_curCol) && columnType(m_curCol) != KexiDB::Field::Boolean) { + KexiTableEdit *edit = tableEditorWidget( m_curCol, /*ignoreMissingEditor=*/true ); + if (edit && edit->handleDoubleClick()) { + //nothing to do: editors like BLOB editor has custom handling of double clicking + } + else { + startEditCurrentCell(); + // createEditor(m_curRow, m_curCol, QString::null); + } + } + + emit itemDblClicked(m_currentItem, m_curRow, m_curCol); + } +} + +void KexiTableView::contentsMousePressEvent( QMouseEvent* e ) +{ +// kdDebug(44021) << "KexiTableView::contentsMousePressEvent() ??" << endl; + setFocus(); + if(m_data->count()==0 && !isInsertingEnabled()) { + QScrollView::contentsMousePressEvent( e ); + return; + } + + if (columnAt(e->pos().x())==-1) { //outside a colums + QScrollView::contentsMousePressEvent( e ); + return; + } +// d->contentsMousePressEvent_ev = *e; +// d->contentsMousePressEvent_enabled = true; +// QTimer::singleShot(2000, this, SLOT( contentsMousePressEvent_Internal() )); +// d->contentsMousePressEvent_timer.start(100,true); + +// if (!d->contentsMousePressEvent_enabled) +// return; +// d->contentsMousePressEvent_enabled=false; + + if (!d->moveCursorOnMouseRelease) { + if (!handleContentsMousePressOrRelease(e, false)) + return; + } + +// kdDebug(44021)<<"void KexiTableView::contentsMousePressEvent( QMouseEvent* e ) by now the current items should be set, if not -> error + crash"<<endl; + if(e->button() == Qt::RightButton) + { + showContextMenu(e->globalPos()); + } + else if(e->button() == Qt::LeftButton) + { + if(columnType(m_curCol) == KexiDB::Field::Boolean && columnEditable(m_curCol)) + { + //only accept clicking on the [x] rect (copied from KexiBoolTableEdit::setupContents()) + int s = QMAX(d->rowHeight - 5, 12); + s = QMIN( d->rowHeight-3, s ); + s = QMIN( columnWidth(m_curCol)-3, s ); //avoid too large box + const QRect r( columnPos(m_curCol) + QMAX( columnWidth(m_curCol)/2 - s/2, 0 ), rowPos(m_curRow) +d->rowHeight/2 - s/2 /*- 1*/, s, s); + //kexidbg << r << endl; + if (r.contains(e->pos())) { +// kexidbg << "e->x:" << e->x() << " e->y:" << e->y() << " " << rowPos(m_curRow) << +// " " << columnPos(m_curCol) << endl; + boolToggled(); + } + } +#if 0 //js: TODO + else if(columnType(m_curCol) == QVariant::StringList && columnEditable(m_curCol)) + { + createEditor(m_curRow, m_curCol); + } +#endif + } +//ScrollView::contentsMousePressEvent( e ); +} + +void KexiTableView::contentsMouseReleaseEvent( QMouseEvent* e ) +{ +// kdDebug(44021) << "KexiTableView::contentsMousePressEvent() ??" << endl; + if(m_data->count()==0 && !isInsertingEnabled()) + return; + + if (d->moveCursorOnMouseRelease) + handleContentsMousePressOrRelease(e, true); + + int col = columnAt(e->pos().x()); + int row = rowAt(e->pos().y()); + + if (!m_currentItem || col==-1 || row==-1 || col!=m_curCol || row!=m_curRow)//outside a current cell + return; + + QScrollView::contentsMouseReleaseEvent( e ); + + emit itemMouseReleased(m_currentItem, m_curRow, m_curCol); +} + +bool KexiTableView::handleContentsMousePressOrRelease(QMouseEvent* e, bool release) +{ + // remember old focus cell + int oldRow = m_curRow; + int oldCol = m_curCol; + kdDebug(44021) << "oldRow=" << oldRow <<" oldCol=" << oldCol <<endl; + bool onInsertItem = false; + + int newrow, newcol; + //compute clicked row nr + if (isInsertingEnabled()) { + if (rowAt(e->pos().y())==-1) { + newrow = rowAt(e->pos().y() - d->rowHeight); + if (newrow==-1 && m_data->count()>0) { + if (release) + QScrollView::contentsMouseReleaseEvent( e ); + else + QScrollView::contentsMousePressEvent( e ); + return false; + } + newrow++; + kdDebug(44021) << "Clicked just on 'insert' row." << endl; + onInsertItem=true; + } + else { + // get new focus cell + newrow = rowAt(e->pos().y()); + } + } + else { + if (rowAt(e->pos().y())==-1 || columnAt(e->pos().x())==-1) { + if (release) + QScrollView::contentsMouseReleaseEvent( e ); + else + QScrollView::contentsMousePressEvent( e ); + return false; //clicked outside a grid + } + // get new focus cell + newrow = rowAt(e->pos().y()); + } + newcol = columnAt(e->pos().x()); + + if(e->button() != Qt::NoButton) { + setCursorPosition(newrow,newcol); + } + return true; +} + +void KexiTableView::showContextMenu(const QPoint& _pos) +{ + if (!d->contextMenuEnabled || m_popupMenu->count()<1) + return; + QPoint pos(_pos); + if (pos==QPoint(-1,-1)) { + pos = viewport()->mapToGlobal( QPoint( columnPos(m_curCol), rowPos(m_curRow) + d->rowHeight ) ); + } + //show own context menu if configured +// if (updateContextMenu()) { + selectRow(m_curRow); + m_popupMenu->exec(pos); +/* } + else { + //request other context menu + emit contextMenuRequested(m_currentItem, m_curCol, pos); + }*/ +} + +void KexiTableView::contentsMouseMoveEvent( QMouseEvent *e ) +{ + int row; + const int col = columnAt(e->x()); + if (col < 0) { + row = -1; + } else { + row = rowAt( e->y(), true /*ignoreEnd*/ ); + if (row > (rows() - 1 + (isInsertingEnabled()?1:0))) + row = -1; //no row to paint + } +// kexidbg << " row="<<row<< " col="<<col<<endl; + //update row highlight if needed + if (d->appearance.rowMouseOverHighlightingEnabled) { + if (row != d->highlightedRow) { + const int oldRow = d->highlightedRow; + d->highlightedRow = row; + updateRow(oldRow); + updateRow(d->highlightedRow); + //currently selected (not necessary highlighted) row needs to be repainted + updateRow(m_curRow); + m_verticalHeader->setHighlightedRow(d->highlightedRow); + } + } + +#if 0//(js) doesn't work! + + // do the same as in mouse press + int x,y; + contentsToViewport(e->x(), e->y(), x, y); + + if(y > visibleHeight()) + { + d->needAutoScroll = true; + d->scrollTimer->start(70, false); + d->scrollDirection = ScrollDown; + } + else if(y < 0) + { + d->needAutoScroll = true; + d->scrollTimer->start(70, false); + d->scrollDirection = ScrollUp; + } + else if(x > visibleWidth()) + { + d->needAutoScroll = true; + d->scrollTimer->start(70, false); + d->scrollDirection = ScrollRight; + } + else if(x < 0) + { + d->needAutoScroll = true; + d->scrollTimer->start(70, false); + d->scrollDirection = ScrollLeft; + } + else + { + d->needAutoScroll = false; + d->scrollTimer->stop(); + contentsMousePressEvent(e); + } +#endif + QScrollView::contentsMouseMoveEvent(e); +} + +#if 0//(js) doesn't work! +void KexiTableView::contentsMouseReleaseEvent(QMouseEvent *) +{ + if(d->needAutoScroll) + { + d->scrollTimer->stop(); + } +} +#endif + +static bool overrideEditorShortcutNeeded(QKeyEvent *e) +{ + //perhaps more to come... + return e->key() == Qt::Key_Delete && e->state()==Qt::ControlButton; +} + +bool KexiTableView::shortCutPressed( QKeyEvent *e, const QCString &action_name ) +{ + const int k = e->key(); + KAction *action = m_sharedActions[action_name]; + if (action) { + if (!action->isEnabled())//this action is disabled - don't process it! + return false; + if (action->shortcut() == KShortcut( KKey(e) )) { + //special cases when we need to override editor's shortcut + if (overrideEditorShortcutNeeded(e)) { + return true; + } + return false;//this shortcut is owned by shared action - don't process it! + } + } + + //check default shortcut (when user app has no action shortcuts defined + // but we want these shortcuts to still work) + if (action_name=="data_save_row") + return (k == Qt::Key_Return || k == Qt::Key_Enter) && e->state()==Qt::ShiftButton; + if (action_name=="edit_delete_row") + return k == Qt::Key_Delete && e->state()==Qt::ControlButton; + if (action_name=="edit_delete") + return k == Qt::Key_Delete && e->state()==Qt::NoButton; + if (action_name=="edit_edititem") + return k == Qt::Key_F2 && e->state()==Qt::NoButton; + if (action_name=="edit_insert_empty_row") + return k == Qt::Key_Insert && e->state()==(Qt::ShiftButton | Qt::ControlButton); + + return false; +} + +void KexiTableView::keyPressEvent(QKeyEvent* e) +{ + if (!hasData()) + return; +// kexidbg << "KexiTableView::keyPressEvent: key=" <<e->key() << " txt=" <<e->text()<<endl; + + const int k = e->key(); + const bool ro = isReadOnly(); + QWidget *w = focusWidget(); +// if (!w || w!=viewport() && w!=this && (!m_editor || w!=m_editor->view() && w!=m_editor)) { +// if (!w || w!=viewport() && w!=this && (!m_editor || w!=m_editor->view())) { + if (!w || w!=viewport() && w!=this && (!m_editor || !KexiUtils::hasParent(dynamic_cast<QObject*>(m_editor), w))) { + //don't process stranger's events + e->ignore(); + return; + } + if (d->skipKeyPress) { + d->skipKeyPress=false; + e->ignore(); + return; + } + + if(m_currentItem == 0 && (m_data->count() > 0 || isInsertingEnabled())) + { + setCursorPosition(0,0); + } + else if(m_data->count() == 0 && !isInsertingEnabled()) + { + e->accept(); + return; + } + + if(m_editor) {// if a cell is edited, do some special stuff + if (k == Qt::Key_Escape) { + cancelEditor(); + e->accept(); + return; + } else if (k == Qt::Key_Return || k == Qt::Key_Enter) { + if (columnType(m_curCol) == KexiDB::Field::Boolean) { + boolToggled(); + } + else { + acceptEditor(); + } + e->accept(); + return; + } + } + else if (m_rowEditing) {// if a row is in edit mode, do some special stuff + if (shortCutPressed( e, "data_save_row")) { + kexidbg << "shortCutPressed!!!" <<endl; + acceptRowEdit(); + return; + } + } + + if(k == Qt::Key_Return || k == Qt::Key_Enter) + { + emit itemReturnPressed(m_currentItem, m_curRow, m_curCol); + } + + int curRow = m_curRow; + int curCol = m_curCol; + + const bool nobtn = e->state()==NoButton; + bool printable = false; + + //check shared shortcuts + if (!ro) { + if (shortCutPressed(e, "edit_delete_row")) { + deleteCurrentRow(); + e->accept(); + return; + } else if (shortCutPressed(e, "edit_delete")) { + deleteAndStartEditCurrentCell(); + e->accept(); + return; + } + else if (shortCutPressed(e, "edit_insert_empty_row")) { + insertEmptyRow(); + e->accept(); + return; + } + } + +/* case Qt::Key_Delete: + if (e->state()==Qt::ControlButton) {//remove current row + deleteCurrentRow(); + } + else if (nobtn) {//remove contents of the current cell + deleteAndStartEditCurrentCell(); + } + break;*/ + +// bool _return; + if (k == Qt::Key_Shift || k == Qt::Key_Alt || k == Qt::Key_Control || k == Qt::Key_Meta) { + e->ignore(); + } + else if (KexiDataAwareObjectInterface::handleKeyPress(e, curRow, curCol, d->appearance.fullRowSelection)) { + if (e->isAccepted()) + return; + } + else if (k == Qt::Key_Backspace && nobtn) { + if (!ro && columnType(curCol) != KexiDB::Field::Boolean && columnEditable(curCol)) + createEditor(curRow, curCol, QString::null, true); + } + else if (k == Qt::Key_Space) { + if (nobtn && !ro && columnEditable(curCol)) { + if (columnType(curCol) == KexiDB::Field::Boolean) { + boolToggled(); + } + else + printable = true; //just space key + } + } + else if (k == Qt::Key_Escape) { + if (nobtn && m_rowEditing) { + cancelRowEdit(); + return; + } + } + else { + //others: + if (nobtn && (k==Qt::Key_Tab || k==Qt::Key_Right)) { +//! \todo add option for stopping at 1st column for Qt::Key_left + //tab + if (acceptEditor()) { + if (curCol == (columns() - 1)) { + if (curRow < (rows()-1+(isInsertingEnabled()?1:0))) {//skip to next row + curRow++; + curCol = 0; + } + } + else + curCol++; + } + } + else if ((e->state()==Qt::ShiftButton && k==Qt::Key_Tab) + || (nobtn && k==Qt::Key_Backtab) + || (e->state()==Qt::ShiftButton && k==Qt::Key_Backtab) + || (nobtn && k==Qt::Key_Left) + ) { +//! \todo add option for stopping at last column + //backward tab + if (acceptEditor()) { + if (curCol == 0) { + if (curRow>0) {//skip to previous row + curRow--; + curCol = columns() - 1; + } + } + else + curCol--; + } + } + else if (nobtn && k==d->contextMenuKey) { //Qt::Key_Menu: + showContextMenu(); + } + else { + KexiTableEdit *edit = tableEditorWidget( m_curCol ); + if (edit && edit->handleKeyPress(e, m_editor==edit)) { + //try to handle the event @ editor's level + e->accept(); + return; + } + else if ( nobtn && (k==Qt::Key_Enter || k==Qt::Key_Return || shortCutPressed(e, "edit_edititem")) ) { + //this condition is moved after handleKeyPress() to allow to everride enter key as well + startEditOrToggleValue(); + } + else { + kexidbg << "KexiTableView::KeyPressEvent(): default" << endl; + if (e->text().isEmpty() || !e->text().isEmpty() && !e->text()[0].isPrint() ) { + kdDebug(44021) << "NOT PRINTABLE: 0x0" << QString("%1").arg(k,0,16) <<endl; + // e->ignore(); + QScrollView::keyPressEvent(e); + return; + } + printable = true; + } + } + } + //finally: we've printable char: + if (printable && !ro) { + KexiTableViewColumn *tvcol = m_data->column(curCol); + if (tvcol->acceptsFirstChar(e->text()[0])) { + kdDebug(44021) << "KexiTableView::KeyPressEvent(): ev pressed: acceptsFirstChar()==true" << endl; + // if (e->text()[0].isPrint()) + createEditor(curRow, curCol, e->text(), true); + } + else { +//TODO show message "key not allowed eg. on a statusbar" + kdDebug(44021) << "KexiTableView::KeyPressEvent(): ev pressed: acceptsFirstChar()==false" << endl; + } + } + + m_vScrollBarValueChanged_enabled=false; + + // if focus cell changes, repaint + setCursorPosition(curRow, curCol); + + m_vScrollBarValueChanged_enabled=true; + + e->accept(); +} + +void KexiTableView::emitSelected() +{ + if(m_currentItem) + emit itemSelected(m_currentItem); +} + +int KexiTableView::rowsPerPage() const +{ + return visibleHeight() / d->rowHeight; +} + +KexiDataItemInterface *KexiTableView::editor( int col, bool ignoreMissingEditor ) +{ + if (!m_data || col<0 || col>=columns()) + return 0; + KexiTableViewColumn *tvcol = m_data->column(col); +// int t = tvcol->field->type(); + + //find the editor for this column + KexiTableEdit *editor = d->editors[ tvcol ]; + if (editor) + return editor; + + //not found: create +// editor = KexiCellEditorFactory::createEditor(*m_data->column(col)->field, this); + editor = KexiCellEditorFactory::createEditor(*tvcol, this); + if (!editor) {//create error! + if (!ignoreMissingEditor) { + //js TODO: show error??? + cancelRowEdit(); + } + return 0; + } + editor->hide(); + if (m_data->cursor() && m_data->cursor()->query()) + editor->createInternalEditor(*m_data->cursor()->query()); + + connect(editor,SIGNAL(editRequested()),this,SLOT(slotEditRequested())); + connect(editor,SIGNAL(cancelRequested()),this,SLOT(cancelEditor())); + connect(editor,SIGNAL(acceptRequested()),this,SLOT(acceptEditor())); + + editor->resize(columnWidth(col)-1, rowHeight()-1); + editor->installEventFilter(this); + if (editor->widget()) + editor->widget()->installEventFilter(this); + //store + d->editors.insert( tvcol, editor ); + return editor; +} + +void KexiTableView::editorShowFocus( int /*row*/, int col ) +{ + KexiDataItemInterface *edit = editor( col ); + /*nt p = rowPos(row); + (!edit || (p < contentsY()) || (p > (contentsY()+clipper()->height()))) { + kexidbg<< "KexiTableView::editorShowFocus() : OUT" << endl; + return; + }*/ + if (edit) { + kexidbg<< "KexiTableView::editorShowFocus() : IN" << endl; + QRect rect = cellGeometry( m_curRow, m_curCol ); +// rect.moveBy( -contentsX(), -contentsY() ); + edit->showFocus( rect, isReadOnly() || m_data->column(col)->isReadOnly() ); + } +} + +void KexiTableView::slotEditRequested() +{ + createEditor(m_curRow, m_curCol); +} + +void KexiTableView::reloadData() { + KexiDataAwareObjectInterface::reloadData(); + updateContents(); +} + +void KexiTableView::createEditor(int row, int col, const QString& addText, bool removeOld) +{ + kdDebug(44021) << "KexiTableView::createEditor('"<<addText<<"',"<<removeOld<<")"<<endl; + if (isReadOnly()) { + kdDebug(44021) << "KexiTableView::createEditor(): DATA IS READ ONLY!"<<endl; + return; + } + + if (m_data->column(col)->isReadOnly()) {//d->pColumnModes.at(d->numCols-1) & ColumnReadOnly) + kdDebug(44021) << "KexiTableView::createEditor(): COL IS READ ONLY!"<<endl; + return; + } + + const bool startRowEdit = !m_rowEditing; //remember if we're starting row edit + + if (!m_rowEditing) { + //we're starting row editing session + m_data->clearRowEditBuffer(); + + m_rowEditing = true; + //indicate on the vheader that we are editing: + m_verticalHeader->setEditRow(m_curRow); + if (isInsertingEnabled() && m_currentItem==m_insertItem) { + //we should know that we are in state "new row editing" + m_newRowEditing = true; + //'insert' row editing: show another row after that: + m_data->append( m_insertItem ); + //new empty 'inserting' item + m_insertItem = m_data->createItem(); + m_verticalHeader->addLabel(); + m_verticalHeaderAlreadyAdded = true; + updateWidgetContentsSize(); + //refr. current and next row + updateContents(columnPos(0), rowPos(row), viewport()->width(), d->rowHeight*2); +// updateContents(columnPos(0), rowPos(row+1), viewport()->width(), d->rowHeight); +//js: warning this breaks behaviour (cursor is skipping, etc.): qApp->processEvents(500); + ensureVisible(columnPos(m_curCol), rowPos(row+1)+d->rowHeight-1, columnWidth(m_curCol), d->rowHeight); + + m_verticalHeader->setOffset(contentsY()); + } + } + + KexiTableEdit *editorWidget = tableEditorWidget( col ); + m_editor = editorWidget; + if (!editorWidget) + return; + + m_editor->setValue(*bufferedValueAt(col, !removeOld/*useDefaultValueIfPossible*/), addText, removeOld); + if (m_editor->hasFocusableWidget()) { + moveChild(editorWidget, columnPos(m_curCol), rowPos(m_curRow)); + + editorWidget->resize(columnWidth(m_curCol)-1, rowHeight()-1); + editorWidget->show(); + + m_editor->setFocus(); + } + + if (startRowEdit) { + m_navPanel->showEditingIndicator(true); //this will allow to enable 'next' btn +// m_navPanel->updateButtons(rows()); //refresh 'next' btn + emit rowEditStarted(m_curRow); + } +} + +void KexiTableView::focusInEvent(QFocusEvent* e) +{ + Q_UNUSED(e); + updateCell(m_curRow, m_curCol); +} + +void KexiTableView::focusOutEvent(QFocusEvent* e) +{ + KexiDataAwareObjectInterface::focusOutEvent(e); +} + +bool KexiTableView::focusNextPrevChild(bool /*next*/) +{ + return false; //special Tab/BackTab meaning +/* if (m_editor) + return true; + return QScrollView::focusNextPrevChild(next);*/ +} + +void KexiTableView::resizeEvent(QResizeEvent *e) +{ + QScrollView::resizeEvent(e); + //updateGeometries(); + + if (m_navPanel) + m_navPanel->updateGeometry(leftMargin()); +// updateNavPanelGeometry(); + + if ((contentsHeight() - e->size().height()) <= d->rowHeight) { + slotUpdate(); + triggerUpdate(); + } +// d->pTopHeader->repaint(); + + +/* m_navPanel->setGeometry( + frameWidth(), + viewport()->height() +d->pTopHeader->height() + -(horizontalScrollBar()->isVisible() ? 0 : horizontalScrollBar()->sizeHint().height()) + +frameWidth(), + m_navPanel->sizeHint().width(), // - verticalScrollBar()->sizeHint().width() - horizontalScrollBar()->sizeHint().width(), + horizontalScrollBar()->sizeHint().height() + );*/ +// updateContents(); +// m_navPanel->setGeometry(1,horizontalScrollBar()->pos().y(), + // m_navPanel->width(), horizontalScrollBar()->height()); +// updateContents(0,0,2000,2000);//js +// erase(); repaint(); +} + +void KexiTableView::viewportResizeEvent( QResizeEvent *e ) +{ + QScrollView::viewportResizeEvent( e ); + updateGeometries(); +// erase(); repaint(); +} + +void KexiTableView::showEvent(QShowEvent *e) +{ + QScrollView::showEvent(e); + if (!d->maximizeColumnsWidthOnShow.isEmpty()) { + maximizeColumnsWidth(d->maximizeColumnsWidthOnShow); + d->maximizeColumnsWidthOnShow.clear(); + } + + if (m_initDataContentsOnShow) { + //full init + m_initDataContentsOnShow = false; + initDataContents(); + } + else { + //just update size + QSize s(tableSize()); +// QRect r(cellGeometry(rows() - 1 + (isInsertingEnabled()?1:0), columns() - 1 )); +// resizeContents(r.right() + 1, r.bottom() + 1); + resizeContents(s.width(),s.height()); + } + updateGeometries(); + + //now we can ensure cell's visibility ( if there was such a call before show() ) + if (d->ensureCellVisibleOnShow!=QPoint(-1,-1)) { + ensureCellVisible( d->ensureCellVisibleOnShow.x(), d->ensureCellVisibleOnShow.y() ); + d->ensureCellVisibleOnShow = QPoint(-1,-1); //reset the flag + } + if (m_navPanel) + m_navPanel->updateGeometry(leftMargin()); +// updateNavPanelGeometry(); +} + +void KexiTableView::contentsDragMoveEvent(QDragMoveEvent *e) +{ + if (!hasData()) + return; + if (m_dropsAtRowEnabled) { + QPoint p = e->pos(); + int row = rowAt(p.y()); + KexiTableItem *item = 0; +// if (row==(rows()-1) && (p.y() % d->rowHeight) > (d->rowHeight*2/3) ) { + if ((p.y() % d->rowHeight) > (d->rowHeight*2/3) ) { + row++; + } + item = m_data->at(row); + emit dragOverRow(item, row, e); + if (e->isAccepted()) { + if (m_dragIndicatorLine>=0 && m_dragIndicatorLine != row) { + //erase old indicator + updateRow(m_dragIndicatorLine); + } + if (m_dragIndicatorLine != row) { + m_dragIndicatorLine = row; + updateRow(m_dragIndicatorLine); + } + } + else { + if (m_dragIndicatorLine>=0) { + //erase old indicator + updateRow(m_dragIndicatorLine); + } + m_dragIndicatorLine = -1; + } + } + else + e->acceptAction(false); +/* QStringList::ConstIterator it, end( d->dropFilters.constEnd() ); + for( it = d->dropFilters.constBegin(); it != end; it++) + { + if(e->provides((*it).latin1())) + { + e->acceptAction(true); + return; + } + }*/ +// e->acceptAction(false); +} + +void KexiTableView::contentsDropEvent(QDropEvent *e) +{ + if (!hasData()) + return; + if (m_dropsAtRowEnabled) { + //we're no longer dragging over the table + if (m_dragIndicatorLine>=0) { + int row2update = m_dragIndicatorLine; + m_dragIndicatorLine = -1; + updateRow(row2update); + } + QPoint p = e->pos(); + int row = rowAt(p.y()); + if ((p.y() % d->rowHeight) > (d->rowHeight*2/3) ) { + row++; + } + KexiTableItem *item = m_data->at(row); + KexiTableItem *newItem = 0; + emit droppedAtRow(item, row, e, newItem); + if (newItem) { + const int realRow = (row==m_curRow ? -1 : row); + insertItem(newItem, realRow); + setCursorPosition(row, 0); +// m_currentItem = newItem; + } + } +} + +void KexiTableView::viewportDragLeaveEvent( QDragLeaveEvent *e ) +{ + Q_UNUSED(e); + if (!hasData()) + return; + if (m_dropsAtRowEnabled) { + //we're no longer dragging over the table + if (m_dragIndicatorLine>=0) { + int row2update = m_dragIndicatorLine; + m_dragIndicatorLine = -1; + updateRow(row2update); + } + } +} + +void KexiTableView::updateCell(int row, int col) +{ +// kdDebug(44021) << "updateCell("<<row<<", "<<col<<")"<<endl; + updateContents(cellGeometry(row, col)); +/* QRect r = cellGeometry(row, col); + r.setHeight(r.height()+6); + r.setTop(r.top()-3); + updateContents();*/ +} + +void KexiTableView::updateCurrentCell() +{ + updateCell(m_curRow, m_curCol); +} + +void KexiTableView::updateRow(int row) +{ +// kdDebug(44021) << "updateRow("<<row<<")"<<endl; + if (row < 0 || row >= (rows() + 2/* sometimes we want to refresh the row after last*/ )) + return; + //int leftcol = d->pTopHeader->sectionAt( d->pTopHeader->offset() ); + + //kexidbg << contentsX() << " " << contentsY() << endl; + //kexidbg << QRect( columnPos( leftcol ), rowPos(row), clipper()->width(), rowHeight() ) << endl; + // updateContents( QRect( columnPos( leftcol ), rowPos(row), clipper()->width(), rowHeight() ) ); //columnPos(rightcol)+columnWidth(rightcol), rowHeight() ) ); + updateContents( QRect( contentsX(), rowPos(row), clipper()->width(), rowHeight() ) ); //columnPos(rightcol)+columnWidth(rightcol), rowHeight() ) ); +} + +void KexiTableView::slotColumnWidthChanged( int, int, int ) +{ + QSize s(tableSize()); + int w = contentsWidth(); + viewport()->setUpdatesEnabled(false); + resizeContents( s.width(), s.height() ); + viewport()->setUpdatesEnabled(true); + if (contentsWidth() < w) { + updateContents(contentsX(), 0, viewport()->width(), contentsHeight()); +// repaintContents( s.width(), 0, w - s.width() + 1, contentsHeight(), true ); + } + else { + // updateContents( columnPos(col), 0, contentsWidth(), contentsHeight() ); + updateContents(contentsX(), 0, viewport()->width(), contentsHeight()); + // viewport()->repaint(); + } + +// updateContents(0, 0, d->pBufferPm->width(), d->pBufferPm->height()); + QWidget *editorWidget = dynamic_cast<QWidget*>(m_editor); + if (editorWidget) + { + editorWidget->resize(columnWidth(m_curCol)-1, rowHeight()-1); + moveChild(editorWidget, columnPos(m_curCol), rowPos(m_curRow)); + } + updateGeometries(); + updateScrollBars(); + if (m_navPanel) + m_navPanel->updateGeometry(leftMargin()); +// updateNavPanelGeometry(); +} + +void KexiTableView::slotSectionHandleDoubleClicked( int section ) +{ + adjustColumnWidthToContents(section); + slotColumnWidthChanged(0,0,0); //to update contents and redraw +} + + +void KexiTableView::updateGeometries() +{ + QSize ts = tableSize(); + if (m_horizontalHeader->offset() && ts.width() < (m_horizontalHeader->offset() + m_horizontalHeader->width())) + horizontalScrollBar()->setValue(ts.width() - m_horizontalHeader->width()); + +// m_verticalHeader->setGeometry(1, topMargin() + 1, leftMargin(), visibleHeight()); + m_horizontalHeader->setGeometry(leftMargin() + 1, 1, visibleWidth(), topMargin()); + m_verticalHeader->setGeometry(1, topMargin() + 1, leftMargin(), visibleHeight()); +} + +int KexiTableView::columnWidth(int col) const +{ + if (!hasData()) + return 0; + int vcID = m_data->visibleColumnID( col ); + return (vcID==-1) ? 0 : m_horizontalHeader->sectionSize( vcID ); +} + +int KexiTableView::rowHeight() const +{ + return d->rowHeight; +} + +int KexiTableView::columnPos(int col) const +{ + if (!hasData()) + return 0; + //if this column is hidden, find first column before that is visible + int c = QMIN(col, (int)m_data->columnsCount()-1), vcID = 0; + while (c>=0 && (vcID=m_data->visibleColumnID( c ))==-1) + c--; + if (c<0) + return 0; + if (c==col) + return m_horizontalHeader->sectionPos(vcID); + return m_horizontalHeader->sectionPos(vcID)+m_horizontalHeader->sectionSize(vcID); +} + +int KexiTableView::rowPos(int row) const +{ + return d->rowHeight*row; +} + +int KexiTableView::columnAt(int pos) const +{ + if (!hasData()) + return -1; + int r = m_horizontalHeader->sectionAt(pos); + if (r<0) + return r; + return m_data->globalColumnID( r ); + +// if (r==-1) +// kexidbg << "columnAt("<<pos<<")==-1 !!!" << endl; +// return r; +} + +int KexiTableView::rowAt(int pos, bool ignoreEnd) const +{ + if (!hasData()) + return -1; + pos /=d->rowHeight; + if (pos < 0) + return 0; + if ((pos >= (int)m_data->count()) && !ignoreEnd) + return -1; + return pos; +} + +QRect KexiTableView::cellGeometry(int row, int col) const +{ + return QRect(columnPos(col), rowPos(row), + columnWidth(col), rowHeight()); +} + +QSize KexiTableView::tableSize() const +{ + if ((rows()+ (isInsertingEnabled()?1:0) ) > 0 && columns() > 0) { +/* kexidbg << "tableSize()= " << columnPos( columns() - 1 ) + columnWidth( columns() - 1 ) + << ", " << rowPos( rows()-1+(isInsertingEnabled()?1:0)) + d->rowHeight +// + QMAX(m_navPanel ? m_navPanel->height() : 0, horizontalScrollBar()->sizeHint().height()) + + (m_navPanel->isVisible() ? QMAX( m_navPanel->height(), horizontalScrollBar()->sizeHint().height() ) :0 ) + + margin() << endl; +*/ +// kexidbg<< m_navPanel->isVisible() <<" "<<m_navPanel->height()<<" " +// <<horizontalScrollBar()->sizeHint().height()<<" "<<rowPos( rows()-1+(isInsertingEnabled()?1:0))<<endl; + + //int xx = horizontalScrollBar()->sizeHint().height()/2; + + QSize s( + columnPos( columns() - 1 ) + columnWidth( columns() - 1 ), +// + verticalScrollBar()->sizeHint().width(), + rowPos( rows()-1+(isInsertingEnabled()?1:0) ) + d->rowHeight + + (horizontalScrollBar()->isVisible() ? 0 : horizontalScrollBar()->sizeHint().height()) + + d->internal_bottomMargin +// horizontalScrollBar()->sizeHint().height()/2 +// - /*d->bottomMargin */ horizontalScrollBar()->sizeHint().height()*3/2 + +// + ( (m_navPanel && m_navPanel->isVisible() && verticalScrollBar()->isVisible() + // && !horizontalScrollBar()->isVisible()) + // ? horizontalScrollBar()->sizeHint().height() : 0) + +// + QMAX( (m_navPanel && m_navPanel->isVisible()) ? m_navPanel->height() : 0, +// horizontalScrollBar()->isVisible() ? horizontalScrollBar()->sizeHint().height() : 0) + +// + (m_navPanel->isVisible() +// ? QMAX( m_navPanel->height(), horizontalScrollBar()->sizeHint().height() ) :0 ) + +// - (horizontalScrollBar()->isVisible() ? horizontalScrollBar()->sizeHint().height() :0 ) + + margin() +//-2*d->rowHeight + ); + +// kexidbg << rows()-1 <<" "<< (isInsertingEnabled()?1:0) <<" "<< (m_rowEditing?1:0) << " " << s << endl; + return s; +// +horizontalScrollBar()->sizeHint().height() + margin() ); + } + return QSize(0,0); +} + +void KexiTableView::ensureCellVisible(int row, int col/*=-1*/) +{ + if (!isVisible()) { + //the table is invisible: we can't ensure visibility now + d->ensureCellVisibleOnShow = QPoint(row,col); + return; + } + + //quite clever: ensure the cell is visible: + QRect r( columnPos(col==-1 ? m_curCol : col), rowPos(row) +(d->appearance.fullRowSelection?1:0), + columnWidth(col==-1 ? m_curCol : col), rowHeight()); + +/* if (m_navPanel && horizontalScrollBar()->isHidden() && row == rows()-1) { + //when cursor is moved down and navigator covers the cursor's area, + //area is scrolled up + if ((viewport()->height() - m_navPanel->height()) < r.bottom()) { + scrollBy(0,r.bottom() - (viewport()->height() - m_navPanel->height())); + } + }*/ + + if (m_navPanel && m_navPanel->isVisible() && horizontalScrollBar()->isHidden()) { + //a hack: for visible navigator: increase height of the visible rect 'r' + r.setBottom(r.bottom()+m_navPanel->height()); + } + + QPoint pcenter = r.center(); + ensureVisible(pcenter.x(), pcenter.y(), r.width()/2, r.height()/2); +// updateContents(); +// updateNavPanelGeometry(); +// slotUpdate(); +} + +void KexiTableView::updateAfterCancelRowEdit() +{ + KexiDataAwareObjectInterface::updateAfterCancelRowEdit(); + m_navPanel->showEditingIndicator(false); +} + +void KexiTableView::updateAfterAcceptRowEdit() +{ + KexiDataAwareObjectInterface::updateAfterAcceptRowEdit(); + m_navPanel->showEditingIndicator(false); +} + +bool KexiTableView::getVisibleLookupValue(QVariant& cellValue, KexiTableEdit *edit, + KexiTableItem *item, KexiTableViewColumn *tvcol) const +{ + if (edit->columnInfo() && edit->columnInfo()->indexForVisibleLookupValue()!=-1 + && edit->columnInfo()->indexForVisibleLookupValue() < (int)item->count()) + { + const QVariant *visibleFieldValue = 0; + if (m_currentItem == item && m_data->rowEditBuffer()) { + visibleFieldValue = m_data->rowEditBuffer()->at( + *tvcol->visibleLookupColumnInfo, false/*!useDefaultValueIfPossible*/ ); + } + + if (visibleFieldValue) + //(use bufferedValueAt() - try to get buffered visible value for lookup field) + cellValue = *visibleFieldValue; //txt = visibleFieldValue->toString(); + else + cellValue /*txt*/ = item->at( edit->columnInfo()->indexForVisibleLookupValue() ); //.toString(); + return true; + } + return false; +} + +//reimpl. +void KexiTableView::removeEditor() +{ + if (!m_editor) + return; + KexiDataAwareObjectInterface::removeEditor(); + viewport()->setFocus(); +} + +void KexiTableView::slotRowRepaintRequested(KexiTableItem& item) +{ + updateRow( m_data->findRef(&item) ); +} + +//(js) unused +void KexiTableView::slotAutoScroll() +{ + kdDebug(44021) << "KexiTableView::slotAutoScroll()" <<endl; + if (!d->needAutoScroll) + return; + + switch(d->scrollDirection) + { + case ScrollDown: + setCursorPosition(m_curRow + 1, m_curCol); + break; + + case ScrollUp: + setCursorPosition(m_curRow - 1, m_curCol); + break; + case ScrollLeft: + setCursorPosition(m_curRow, m_curCol - 1); + break; + + case ScrollRight: + setCursorPosition(m_curRow, m_curCol + 1); + break; + } +} + +#ifndef KEXI_NO_PRINT +void +KexiTableView::print(KPrinter &/*printer*/) +{ +// printer.setFullPage(true); +#if 0 + int leftMargin = printer.margins().width() + 2 + d->rowHeight; + int topMargin = printer.margins().height() + 2; +// int bottomMargin = topMargin + ( printer.realPageSize()->height() * printer.resolution() + 36 ) / 72; + int bottomMargin = 0; + kdDebug(44021) << "KexiTableView::print: bottom = " << bottomMargin << endl; + + QPainter p(&printer); + + KexiTableItem *i; + int width = leftMargin; + for(int col=0; col < columns(); col++) + { + p.fillRect(width, topMargin - d->rowHeight, columnWidth(col), d->rowHeight, QBrush(Qt::gray)); + p.drawRect(width, topMargin - d->rowHeight, columnWidth(col), d->rowHeight); + p.drawText(width, topMargin - d->rowHeight, columnWidth(col), d->rowHeight, Qt::AlignLeft | Qt::AlignVCenter, + m_horizontalHeader->label(col)); + width = width + columnWidth(col); + } + + int yOffset = topMargin; + int row = 0; + int right = 0; + for(i = m_data->first(); i; i = m_data->next()) + { + if(!i->isInsertItem()) + { kdDebug(44021) << "KexiTableView::print: row = " << row << " y = " << yOffset << endl; + int xOffset = leftMargin; + for(int col=0; col < columns(); col++) + { + kdDebug(44021) << "KexiTableView::print: col = " << col << " x = " << xOffset << endl; + p.saveWorldMatrix(); + p.translate(xOffset, yOffset); + paintCell(&p, i, col, QRect(0, 0, columnWidth(col) + 1, d->rowHeight), true); + p.restoreWorldMatrix(); +// p.drawRect(xOffset, yOffset, columnWidth(col), d->rowHeight); + xOffset = xOffset + columnWidth(col); + right = xOffset; + } + + row++; + yOffset = topMargin + row * d->rowHeight; + } + + if(yOffset > 900) + { + p.drawLine(leftMargin, topMargin, leftMargin, yOffset); + p.drawLine(leftMargin, topMargin, right - 1, topMargin); + printer.newPage(); + yOffset = topMargin; + row = 0; + } + } + p.drawLine(leftMargin, topMargin, leftMargin, yOffset); + p.drawLine(leftMargin, topMargin, right - 1, topMargin); + +// p.drawLine(60,60,120,150); + p.end(); +#endif +} +#endif + +QString KexiTableView::columnCaption(int colNum) const +{ + return m_horizontalHeader->label(colNum); +} + +KexiDB::Field* KexiTableView::field(int colNum) const +{ + if (!m_data || !m_data->column(colNum)) + return 0; + return m_data->column(colNum)->field(); +} + +void KexiTableView::adjustColumnWidthToContents(int colNum) +{ + if (!hasData()) + return; + if (colNum==-1) { + const int cols = columns(); + for (int i=0; i<cols; i++) + adjustColumnWidthToContents(i); + return; + } + + int indexOfVisibleColumn = (m_data->column(colNum) && m_data->column(colNum)->columnInfo) + ? m_data->column(colNum)->columnInfo->indexForVisibleLookupValue() : -1; + if (-1==indexOfVisibleColumn) + indexOfVisibleColumn = colNum; + + if (indexOfVisibleColumn < 0) + return; + + QPtrListIterator<KexiTableItem> it = m_data->iterator(); + if (it.current() && it.current()->count()<=(uint)indexOfVisibleColumn) + return; + + KexiCellEditorFactoryItem *item = KexiCellEditorFactory::item( columnType(indexOfVisibleColumn) ); + if (!item) + return; + QFontMetrics fm(fontMetrics()); + int maxw = horizontalHeaderVisible() + ? fm.width( m_horizontalHeader->label( colNum/* not indexOfVisibleColumn*/ ) ) : 0; + if (maxw == 0 && m_data->isEmpty()) + return; //nothing to adjust + +//! \todo js: this is NOT EFFECTIVE for big data sets!!!! + + KexiTableEdit *ed = tableEditorWidget( colNum/* not indexOfVisibleColumn*/ ); + if (ed) { + for (it = m_data->iterator(); it.current(); ++it) { + const int wfw = ed->widthForValue( it.current()->at( indexOfVisibleColumn ), fm ); + maxw = QMAX( maxw, wfw ); + } + const bool focused = currentColumn() == colNum; + maxw += (fm.width(" ") + ed->leftMargin() + ed->rightMargin(focused)); + } + if (maxw < KEXITV_MINIMUM_COLUMN_WIDTH ) + maxw = KEXITV_MINIMUM_COLUMN_WIDTH; //not too small + kexidbg << "KexiTableView: setColumnWidth(colNum=" << colNum + << ", indexOfVisibleColumn=" << indexOfVisibleColumn << ", width=" << maxw <<" )" << endl; + setColumnWidth( colNum/* not indexOfVisibleColumn*/, maxw ); +} + +void KexiTableView::setColumnWidth(int colNum, int width) +{ + if (columns()<=colNum || colNum < 0) + return; + const int oldWidth = m_horizontalHeader->sectionSize( colNum ); + m_horizontalHeader->resizeSection( colNum, width ); + slotTopHeaderSizeChange( colNum, oldWidth, m_horizontalHeader->sectionSize( colNum ) ); +} + +void KexiTableView::maximizeColumnsWidth( const QValueList<int> &columnList ) +{ + if (!isVisible()) { + d->maximizeColumnsWidthOnShow += columnList; + return; + } + if (width() <= m_horizontalHeader->headerWidth()) + return; + //sort the list and make it unique + QValueList<int> cl, sortedList = columnList; + qHeapSort(sortedList); + int i=-999; + + QValueList<int>::ConstIterator it, end( sortedList.constEnd() ); + for ( it = sortedList.constBegin(); it != end; ++it) { + if (i != (*it)) { + cl += (*it); + i = (*it); + } + } + //resize + int sizeToAdd = (width() - m_horizontalHeader->headerWidth()) / cl.count() - verticalHeader()->width(); + if (sizeToAdd<=0) + return; + end = cl.constEnd(); + for ( it = cl.constBegin(); it != end; ++it) { + int w = m_horizontalHeader->sectionSize(*it); + if (w>0) { + m_horizontalHeader->resizeSection(*it, w+sizeToAdd); + } + } + updateContents(); + editorShowFocus( m_curRow, m_curCol ); +} + +void KexiTableView::adjustHorizontalHeaderSize() +{ + m_horizontalHeader->adjustHeaderSize(); +} + +void KexiTableView::setColumnStretchEnabled( bool set, int colNum ) +{ + m_horizontalHeader->setStretchEnabled( set, colNum ); +} + +void KexiTableView::setEditableOnDoubleClick(bool set) +{ + d->editOnDoubleClick = set; +} +bool KexiTableView::editableOnDoubleClick() const +{ + return d->editOnDoubleClick; +} + +bool KexiTableView::verticalHeaderVisible() const +{ + return m_verticalHeader->isVisible(); +} + +void KexiTableView::setVerticalHeaderVisible(bool set) +{ + int left_width; + if (set) { + m_verticalHeader->show(); + left_width = QMIN(m_horizontalHeader->sizeHint().height(), d->rowHeight); + } + else { + m_verticalHeader->hide(); + left_width = 0; + } + setMargins( left_width, horizontalHeaderVisible() ? m_horizontalHeader->sizeHint().height() : 0, 0, 0); +} + +bool KexiTableView::horizontalHeaderVisible() const +{ + return d->horizontalHeaderVisible; +} + +void KexiTableView::setHorizontalHeaderVisible(bool set) +{ + int top_height; + d->horizontalHeaderVisible = set; //needed because isVisible() is not always accurate + if (set) { + m_horizontalHeader->show(); + top_height = m_horizontalHeader->sizeHint().height(); + } + else { + m_horizontalHeader->hide(); + top_height = 0; + } + setMargins( verticalHeaderVisible() ? m_verticalHeader->width() : 0, top_height, 0, 0); +} + +void KexiTableView::triggerUpdate() +{ +// kdDebug(44021) << "KexiTableView::triggerUpdate()" << endl; +// if (!d->pUpdateTimer->isActive()) + d->pUpdateTimer->start(20, true); +// d->pUpdateTimer->start(200, true); +} + +void KexiTableView::setHBarGeometry( QScrollBar & hbar, int x, int y, int w, int h ) +{ +/*todo*/ + kdDebug(44021)<<"KexiTableView::setHBarGeometry"<<endl; + if (d->appearance.navigatorEnabled) { + m_navPanel->setHBarGeometry( hbar, x, y, w, h ); + } + else { + hbar.setGeometry( x , y, w, h ); + } +} + +void KexiTableView::setSpreadSheetMode() +{ + KexiDataAwareObjectInterface::setSpreadSheetMode(); + //copy m_navPanelEnabled flag + Appearance a = d->appearance; + a.navigatorEnabled = m_navPanelEnabled; + setAppearance( a ); +} + +int KexiTableView::validRowNumber(const QString& text) +{ + bool ok=true; + int r = text.toInt(&ok); + if (!ok || r<1) + r = 1; + else if (r > (rows()+(isInsertingEnabled()?1:0))) + r = rows()+(isInsertingEnabled()?1:0); + return r-1; +} + +void KexiTableView::moveToRecordRequested( uint r ) +{ + if (r > uint(rows()+(isInsertingEnabled()?1:0))) + r = rows()+(isInsertingEnabled()?1:0); + setFocus(); + selectRow( r ); +} + +void KexiTableView::moveToLastRecordRequested() +{ + setFocus(); + selectRow(rows()>0 ? (rows()-1) : 0); +} + +void KexiTableView::moveToPreviousRecordRequested() +{ + setFocus(); + selectPrevRow(); +} + +void KexiTableView::moveToNextRecordRequested() +{ + setFocus(); + selectNextRow(); +} + +void KexiTableView::moveToFirstRecordRequested() +{ + setFocus(); + selectFirstRow(); +} + +void KexiTableView::copySelection() +{ + if (m_currentItem && m_curCol!=-1) { + KexiTableEdit *edit = tableEditorWidget( m_curCol ); + QVariant defaultValue; + const bool defaultValueDisplayed + = isDefaultValueDisplayed(m_currentItem, m_curCol, &defaultValue); + if (edit) { + QVariant visibleValue; + getVisibleLookupValue(visibleValue, edit, m_currentItem, m_data->column(m_curCol)); + edit->handleCopyAction( + defaultValueDisplayed ? defaultValue : m_currentItem->at( m_curCol ), + visibleValue ); + } + } +} + +void KexiTableView::cutSelection() +{ + //try to handle @ editor's level + KexiTableEdit *edit = tableEditorWidget( m_curCol ); + if (edit) + edit->handleAction("edit_cut"); +} + +void KexiTableView::paste() +{ + //try to handle @ editor's level + KexiTableEdit *edit = tableEditorWidget( m_curCol ); + if (edit) + edit->handleAction("edit_paste"); +} + +bool KexiTableView::eventFilter( QObject *o, QEvent *e ) +{ + //don't allow to stole key my events by others: +// kexidbg << "spontaneous " << e->spontaneous() << " type=" << e->type() << endl; + + if (e->type()==QEvent::KeyPress) { + if (e->spontaneous() /*|| e->type()==QEvent::AccelOverride*/) { + QKeyEvent *ke = static_cast<QKeyEvent*>(e); + const int k = ke->key(); + int s = ke->state(); + //cell editor's events: + //try to handle the event @ editor's level + KexiTableEdit *edit = tableEditorWidget( m_curCol ); + if (edit && edit->handleKeyPress(ke, m_editor==edit)) { + ke->accept(); + return true; + } + else if (m_editor && (o==dynamic_cast<QObject*>(m_editor) || o==m_editor->widget())) { + if ( (k==Qt::Key_Tab && (s==Qt::NoButton || s==Qt::ShiftButton)) + || (overrideEditorShortcutNeeded(ke)) + || (k==Qt::Key_Enter || k==Qt::Key_Return || k==Qt::Key_Up || k==Qt::Key_Down) + || (k==Qt::Key_Left && m_editor->cursorAtStart()) + || (k==Qt::Key_Right && m_editor->cursorAtEnd()) + ) + { + //try to steal the key press from editor or it's internal widget... + keyPressEvent(ke); + if (ke->isAccepted()) + return true; + } + } + /* + else if (e->type()==QEvent::KeyPress && (o==this || (m_editor && o==m_editor->widget()))){//|| o==viewport()) + keyPressEvent(ke); + if (ke->isAccepted()) + return true; + }*/ +/*todo else if ((k==Qt::Key_Tab || k==(Qt::SHIFT|Qt::Key_Tab)) && o==d->navRowNumber) { + //tab key focuses tv + ke->accept(); + setFocus(); + return true; + }*/ + } + } + else if (o==horizontalScrollBar()) { + if ((e->type()==QEvent::Show && !horizontalScrollBar()->isVisible()) + || (e->type()==QEvent::Hide && horizontalScrollBar()->isVisible())) { + updateWidgetContentsSize(); + } + } + else if (e->type()==QEvent::Leave) { + if (o==viewport() && d->appearance.rowMouseOverHighlightingEnabled + && d->appearance.persistentSelections) + { + if (d->highlightedRow!=-1) { + int oldRow = d->highlightedRow; + d->highlightedRow = -1; + updateRow(oldRow); + const bool dontPaintNonpersistentSelectionBecauseDifferentRowHasBeenHighlighted + = d->appearance.rowHighlightingEnabled && !d->appearance.persistentSelections; + if (oldRow!=m_curRow && m_curRow>=0) { + if (!dontPaintNonpersistentSelectionBecauseDifferentRowHasBeenHighlighted) + //no highlight for now: show selection again + updateRow(m_curRow); + m_verticalHeader->setHighlightedRow(-1); + } + } + } + d->recentCellWithToolTip = QPoint(-1,-1); + } +/* else if (e->type()==QEvent::FocusOut && o->inherits("QWidget")) { + //hp==true if currently focused widget is a child of this table view + const bool hp = KexiUtils::hasParent( static_cast<QWidget*>(o), focusWidget()); + if (!hp && KexiUtils::hasParent( this, static_cast<QWidget*>(o))) { + //accept row editing if focus is moved to foreign widget + //(not a child, like eg. editor) from one of our table view's children + //or from table view itself + if (!acceptRowEdit()) { + static_cast<QWidget*>(o)->setFocus(); + return true; + } + } + }*/ + return QScrollView::eventFilter(o,e); +} + +void KexiTableView::slotTopHeaderSizeChange( + int /*section*/, int /*oldSize*/, int /*newSize*/ ) +{ + editorShowFocus( m_curRow, m_curCol ); +} + +void KexiTableView::setBottomMarginInternal(int pixels) +{ + d->internal_bottomMargin = pixels; +} + +void KexiTableView::paletteChange( const QPalette &oldPalette ) +{ + Q_UNUSED(oldPalette); + //update: + if (m_verticalHeader) + m_verticalHeader->setSelectionBackgroundColor( palette().active().highlight() ); + if (m_horizontalHeader) + m_horizontalHeader->setSelectionBackgroundColor( palette().active().highlight() ); +} + +const KexiTableView::Appearance& KexiTableView::appearance() const +{ + return d->appearance; +} + +void KexiTableView::setAppearance(const Appearance& a) +{ +// if (d->appearance.fullRowSelection != a.fullRowSelection) { + if (a.fullRowSelection) { + d->rowHeight -= 1; + } + else { + d->rowHeight += 1; + } + if (m_verticalHeader) + m_verticalHeader->setCellHeight(d->rowHeight); + if (m_horizontalHeader) { + setMargins( + QMIN(m_horizontalHeader->sizeHint().height(), d->rowHeight), + m_horizontalHeader->sizeHint().height(), 0, 0); + } +// } + if (a.rowHighlightingEnabled) + m_updateEntireRowWhenMovingToOtherRow = true; + + if(!a.navigatorEnabled) + m_navPanel->hide(); + else + m_navPanel->show(); +// } + + d->highlightedRow = -1; +//! @todo is setMouseTracking useful for other purposes? + viewport()->setMouseTracking(a.rowMouseOverHighlightingEnabled); + + d->appearance = a; + + setFont(font()); //this also updates contents +} + +int KexiTableView::highlightedRow() const +{ + return d->highlightedRow; +} + +void KexiTableView::setHighlightedRow(int row) +{ + if (row!=-1) { + row = QMIN(rows() - 1 + (isInsertingEnabled()?1:0), row); + row = QMAX(0, row); + ensureCellVisible(row, -1); + } + const int previouslyHighlightedRow = d->highlightedRow; + if (previouslyHighlightedRow == row) { + if (previouslyHighlightedRow!=-1) + updateRow(previouslyHighlightedRow); + return; + } + d->highlightedRow = row; + if (d->highlightedRow!=-1) + updateRow(d->highlightedRow); + + if (previouslyHighlightedRow!=-1) + updateRow(previouslyHighlightedRow); + + if (m_curRow>=0 && (previouslyHighlightedRow==-1 || previouslyHighlightedRow==m_curRow) + && d->highlightedRow!=m_curRow && !d->appearance.persistentSelections) + { + //currently selected row needs to be repainted + updateRow(m_curRow); + } +} + +KexiTableItem *KexiTableView::highlightedItem() const +{ + return d->highlightedRow == -1 ? 0 : m_data->at(d->highlightedRow); +} + +void KexiTableView::slotSettingsChanged(int category) +{ + if (category==KApplication::SETTINGS_SHORTCUTS) { + d->contextMenuKey = KGlobalSettings::contextMenuKey(); + } +} + +int KexiTableView::lastVisibleRow() const +{ + return rowAt( contentsY() ); +} + +#include "kexitableview.moc" + diff --git a/kexi/widget/tableview/kexitableview.h b/kexi/widget/tableview/kexitableview.h new file mode 100644 index 00000000..9f9c632e --- /dev/null +++ b/kexi/widget/tableview/kexitableview.h @@ -0,0 +1,639 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Till Busch <till@bux.at> + Copyright (C) 2003 Lucijan Busch <lucijan@gmx.at> + Copyright (C) 2003 Daniel Molkentin <molkentin@kde.org> + Copyright (C) 2003 Joseph Wenninger <jowenn@kde.org> + Copyright (C) 2003-2006 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. + + Original Author: Till Busch <till@bux.at> + Original Project: buX (www.bux.at) +*/ + +#ifndef KEXITABLEVIEW_H +#define KEXITABLEVIEW_H + +#include <qscrollview.h> +#include <qvariant.h> +#include <qptrlist.h> +#include <qheader.h> +#include <qtooltip.h> + +#include <kdebug.h> + +#include "kexitableviewdata.h" +#include "kexitableedit.h" +#include <kexiutils/tristate.h> +#include <widget/utils/kexirecordnavigator.h> +#include <widget/utils/kexisharedactionclient.h> +#include "kexidataawareobjectiface.h" + +class KPopupMenu; +class KPrinter; +class KAction; + +class KexiTableHeader; +class KexiTableItem; +class KexiTableView; +class KexiTableEdit; +class KexiTableViewPrivate; +class KActionCollection; + +namespace KexiDB { + class RowEditBuffer; +} + +//! minimum column width in pixels +#define KEXITV_MINIMUM_COLUMN_WIDTH 10 + +//! @short KexiTableView class provides a widget for displaying data in a tabular view. +/*! @see KexiFormScrollView +*/ +class KEXIDATATABLE_EXPORT KexiTableView : + public QScrollView, + public KexiRecordNavigatorHandler, + public KexiSharedActionClient, + public KexiDataAwareObjectInterface +{ +Q_OBJECT +KEXI_DATAAWAREOBJECTINTERFACE +public: + + /*! Defines table view's detailed appearance settings. */ + class KEXIDATATABLE_EXPORT Appearance { + public: + Appearance(QWidget *widget = 0); + + /*! base color for cells, default is "Base" color for application's + current active palette */ + QColor baseColor; + + /*! text color for cells, default is "Text" color for application's + current active palette */ + QColor textColor; + + /*! border color for cells, default is QColor(200,200,200) */ + QColor borderColor; + + /*! empty area color, default is "Base" color for application's + current active palette */ + QColor emptyAreaColor; + + /*! alternate background color, default is KGlobalSettings::alternateBackgroundColor() */ + QColor alternateBackgroundColor; + + /*! true if background altering should be enabled, true by default */ + bool backgroundAltering : 1; + + /*! true if full-row-selection mode is set, + what means that all cells of the current row are always selected, instead of single cell. + This mode is usable for read-only table views, when we're interested only in navigating + by rows. False by default, even for read-only table views. + */ + bool fullRowSelection : 1; + + /*! true if fullgrid is enabled. True by default. + It is set to false for comboboxpopup table, to mimic original + combobox look and feel. */ + bool gridEnabled : 1; + + /*! \if the navigation panel is enabled (visible) for the view. + True by default. */ + bool navigatorEnabled : 1; + + /*! true if "row highlight" behaviour is enabled. False by default. */ + bool rowHighlightingEnabled : 1; + + /*! true if "row highlight over " behaviour is enabled. False by default. */ + bool rowMouseOverHighlightingEnabled : 1; + + /*! true if selection of a row should be kept when a user moved mouse + pointer over other rows. Makes only sense when rowMouseOverHighlightingEnabled is true. + True by default. It is set to false for comboboxpopup table, to mimic original + combobox look and feel. */ + bool persistentSelections : 1; + + /*! color for row highlight, default is intermediate (33%/60%) between + active highlight and base color. */ + QColor rowHighlightingColor; + + /*! color for text under row highlight, default is the same as textColor. + Used when rowHighlightingEnabled is true; */ + QColor rowHighlightingTextColor; + + /*! color for row highlight for mouseover, default is intermediate (20%/80%) between + active highlight and base color. Used when rowMouseOverHighlightingEnabled is true. */ + QColor rowMouseOverHighlightingColor; + + /*! color for text under row highlight for mouseover, default is the same as textColor. + Used when rowMouseOverHighlightingEnabled is true; */ + QColor rowMouseOverHighlightingTextColor; + + /*! Like rowMouseOverHighlightingColor but for areas painted with alternate color. + This is computed using active highlight color and alternateBackgroundColor. */ + QColor rowMouseOverAlternateHighlightingColor; + }; + + KexiTableView(KexiTableViewData* data=0, QWidget* parent=0, const char* name=0); + virtual ~KexiTableView(); + + /*! \return current appearance settings */ + const Appearance& appearance() const; + + /*! Sets appearance settings. Table view is updated automatically. */ + void setAppearance(const Appearance& a); + + /*! \return string displayed for column's header \a colNum */ + QString columnCaption(int colNum) const; + + /*! Convenience function. + \return field object that define column \a colNum or NULL if there is no such column */ + KexiDB::Field* field(int colNum) const; + + /*! Reimplementation for KexiDataAwareObjectInterface */ + virtual void setSpreadSheetMode(); + + /*! \return true if vertical scrollbar's tooltips are enabled (true by default). */ +//moved bool scrollbarToolTipsEnabled() const; + + /*! Enables or disables vertical scrollbar's. */ +//moved void setScrollbarToolTipsEnabled(bool set); + + /*! \return maximum number of rows that can be displayed per one "page" + for current table view's size. */ + virtual int rowsPerPage() const; + + QRect cellGeometry(int row, int col) const; + int columnWidth(int col) const; + int rowHeight() const; + int columnPos(int col) const; + int rowPos(int row) const; + int columnAt(int pos) const; + int rowAt(int pos, bool ignoreEnd=false) const; + + /*! \return last row visible on the screen (counting from 0). + The returned value is guaranteed to be smaller or equal to currentRow() or -1 + if there are no rows. */ + virtual int lastVisibleRow() const; + + /*! Redraws specified cell. */ + virtual void updateCell(int row, int col); + + /*! Redraws the current cell. Implemented after KexiDataAwareObjectInterface. */ + virtual void updateCurrentCell(); + + /*! Redraws all cells of specified row. */ + virtual void updateRow(int row); + + bool editableOnDoubleClick() const; + void setEditableOnDoubleClick(bool set); + + //! \return true if the vertical header is visible + bool verticalHeaderVisible() const; + + //! Sets vertical header's visibility + void setVerticalHeaderVisible(bool set); + + //! \return true if the horizontal header is visible + bool horizontalHeaderVisible() const; + + //! Sets horizontal header's visibility + void setHorizontalHeaderVisible(bool set); + +#ifndef KEXI_NO_PRINT + // printing +// void setupPrinter(KPrinter &printer); + void print(KPrinter &printer); +#endif + + // reimplemented for internal reasons + virtual QSizePolicy sizePolicy() const; + virtual QSize sizeHint() const; + virtual QSize minimumSizeHint() const; + + /*! Reimplemented to update cached fonts and row sizes for the painter. */ + void setFont(const QFont &f); + + virtual QSize tableSize() const; + + void emitSelected(); + + //! single shot after 1ms for contents updatinh + void triggerUpdate(); + + typedef enum ScrollDirection + { + ScrollUp, + ScrollDown, + ScrollLeft, + ScrollRight + }; + + virtual bool eventFilter( QObject *o, QEvent *e ); + + //! Initializes standard editor cell editor factories. This is called internally, once. + static void initCellEditorFactories(); + + /*! \return highlighted row number or -1 if no row is highlighted. + Makes sense if row highlighting is enabled. + @see Appearance::rowHighlightingEnabled setHighlightedRow() */ + int highlightedRow() const; + + KexiTableItem *highlightedItem() const; + + /*! \return vertical scrollbar. Implemented for KexiDataAwareObjectInterface. */ + virtual QScrollBar* verticalScrollBar() const { return QScrollView::verticalScrollBar(); } + +public slots: + virtual void setData( KexiTableViewData *data, bool owner = true ) + { KexiDataAwareObjectInterface::setData( data, owner ); } + + virtual void clearColumnsInternal(bool repaint); + + /*! Adjusts \a colNum column's width to its (current) contents. + If \a colNum == -1, all columns' width is adjusted. */ + void adjustColumnWidthToContents(int colNum = -1); + + //! Sets width of column width to \a width. + void setColumnWidth(int col, int width); + + /*! If \a set is true, \a colNum column is resized to take full possible width. + If \a set is false, no automatic resize will be performed. + If \a colNum is -1, all columns are equally resized, when needed, to take full possible width. + This method behaves like QHeader::setStretchEnabled ( bool b, int section ). */ + void setColumnStretchEnabled( bool set, int colNum ); + + /*! Maximizes widths of columns selected by \a columnList, so the horizontal + header has maximum overall width. Each selected column's width will be increased + by the same value. Does nothing if \a columnList is empty or there is no free space + to resize columns. If this table view is not visible, resizing will be performed on showing. */ + void maximizeColumnsWidth( const QValueList<int> &columnList ); + + /*! Adjusts the size of the sections to fit the size of the horizontal header + as completely as possible. Only sections for which column stretch is enabled will be resized. + \sa setColumnStretchEnabled() QHeader::adjustHeaderSize() */ + void adjustHorizontalHeaderSize(); + + /*! Sets highlighted row number or -1 if no row has to be highlighted. + Makes sense if row highlighting is enabled. + @see Appearance::rowHighlightingEnabled */ + void setHighlightedRow(int row); + + /*! Sets no row that will be highlighted. Equivalent to setHighlightedRow(-1). */ + inline void clearHighlightedRow() { setHighlightedRow(-1); } + + /*! Ensures that cell at \a row and \a col is visible. + If \a col is -1, current column number is used. \a row and \a col (if not -1) must + be between 0 and rows() (or cols() accordingly). */ + virtual void ensureCellVisible(int row, int col/*=-1*/); + +// void gotoNext(); +//js int findString(const QString &string); + + /*! Deletes currently selected record; does nothing if no record + is currently selected. If record is in edit mode, editing + is cancelled before deleting. */ + virtual void deleteCurrentRow() { KexiDataAwareObjectInterface::deleteCurrentRow(); } + + /*! Inserts one empty row above row \a row. If \a row is -1 (the default), + new row is inserted above the current row (or above 1st row if there is no current). + A new item becomes current if row is -1 or if row is equal currentRow(). + This method does nothing if: + -inserting flag is disabled (see isInsertingEnabled()) + -read-only flag is set (see isReadOnly()) + \ return inserted row's data + */ + virtual KexiTableItem *insertEmptyRow(int row = -1) + { return KexiDataAwareObjectInterface::insertEmptyRow(row); } + + /*! Used when Return key is pressed on cell or "+" nav. button is clicked. + Also used when we want to continue editing a cell after "invalid value" message + was displayed (in this case, \a setText is usually not empty, what means + that text will be set in the cell replacing previous value). + */ + virtual void startEditCurrentCell(const QString& setText = QString::null) + { KexiDataAwareObjectInterface::startEditCurrentCell(setText); } + + /*! Deletes currently selected cell's contents, if allowed. + In most cases delete is not accepted immediately but "row editing" mode is just started. */ + virtual void deleteAndStartEditCurrentCell() + { KexiDataAwareObjectInterface::deleteAndStartEditCurrentCell(); } + + /*! Cancels row editing All changes made to the editing + row during this current session will be undone. + \return true on success or false on failure (e.g. when editor does not exist) */ + virtual bool cancelRowEdit() { return KexiDataAwareObjectInterface::cancelRowEdit(); } + + /*! Accepts row editing. All changes made to the editing + row during this current session will be accepted (saved). + \return true if accepting was successful, false otherwise + (e.g. when current row contain data that does not meet given constraints). */ + virtual bool acceptRowEdit() { return KexiDataAwareObjectInterface::acceptRowEdit(); } + + /*! Specifies, if this table view automatically accepts + row editing (using acceptRowEdit()) on accepting any cell's edit + (i.e. after acceptEditor()). \sa acceptsRowEditAfterCellAccepting() */ + virtual void setAcceptsRowEditAfterCellAccepting(bool set) + { KexiDataAwareObjectInterface::setAcceptsRowEditAfterCellAccepting(set); } + + /*! Specifies, if this table accepts dropping data on the rows. + If enabled: + - dragging over row is indicated by drawing a line at bottom side of this row + - dragOverRow() signal will be emitted on dragging, + -droppedAtRow() will be emitted on dropping + By default this flag is set to false. */ + virtual void setDropsAtRowEnabled(bool set) { KexiDataAwareObjectInterface::setDropsAtRowEnabled(set); } + + virtual bool cancelEditor() { return KexiDataAwareObjectInterface::cancelEditor(); } + virtual bool acceptEditor() { return KexiDataAwareObjectInterface::acceptEditor(); } + +signals: + virtual void dataSet( KexiTableViewData *data ); + + virtual void itemSelected(KexiTableItem *); + virtual void cellSelected(int col, int row); + + void itemReturnPressed(KexiTableItem *, int row, int col); + void itemDblClicked(KexiTableItem *, int row, int col); + void itemMouseReleased(KexiTableItem *, int row, int col); + + void dragOverRow(KexiTableItem *item, int row, QDragMoveEvent* e); + void droppedAtRow(KexiTableItem *item, int row, QDropEvent *e, KexiTableItem*& newItem); + + /*! Data has been refreshed on-screen - emitted from initDataContents(). */ + virtual void dataRefreshed(); + + virtual void itemChanged(KexiTableItem *, int row, int col); + virtual void itemChanged(KexiTableItem *, int row, int col, QVariant oldValue); + virtual void itemDeleteRequest(KexiTableItem *, int row, int col); + virtual void currentItemDeleteRequest(); + //! Emitted for spreadsheet mode when an item was deleted and a new item has been appended + virtual void newItemAppendedForAfterDeletingInSpreadSheetMode(); +// void addRecordRequest(); +// void contextMenuRequested(KexiTableItem *, int row, int col, const QPoint &); + void sortedColumnChanged(int col); + + //! emmited when row editing is started (for updating or inserting) + void rowEditStarted(int row); + + //! emmited when row editing is terminated (for updating or inserting) + //! no matter if accepted or not + void rowEditTerminated(int row); + + //! Emitted in initActions() to force reload actions + //! You should remove existing actions and add them again. + void reloadActions(); + +protected slots: + void slotSettingsChanged(int category); + + virtual void slotDataDestroying() { KexiDataAwareObjectInterface::slotDataDestroying(); } + + virtual void slotRowsDeleted( const QValueList<int> & ); + + //! updates display after many rows deletion + void slotColumnWidthChanged( int col, int os, int ns ); + + void slotSectionHandleDoubleClicked( int section ); + + void slotUpdate(); + //! implemented because we needed this as slot + virtual void sortColumnInternal(int col, int order = 0) + { KexiDataAwareObjectInterface::sortColumnInternal(col, order); } + + void slotAutoScroll(); + + //! internal, used when top header's size changed + void slotTopHeaderSizeChange( int section, int oldSize, int newSize ); + + //! receives a signal from cell editors + void slotEditRequested(); + + /*! Reloads data for this widget. + Handles KexiTableViewData::reloadRequested() signal. */ + virtual void reloadData(); + + //! Handles KexiTableViewData::rowRepaintRequested() signal + virtual void slotRowRepaintRequested(KexiTableItem& item); + + //! Handles KexiTableViewData::aboutToDeleteRow() signal. Prepares info for slotRowDeleted(). + virtual void slotAboutToDeleteRow(KexiTableItem& item, KexiDB::ResultInfo* result, bool repaint) + { KexiDataAwareObjectInterface::slotAboutToDeleteRow(item, result, repaint); } + + //! Handles KexiTableViewData::rowDeleted() signal to repaint when needed. + virtual void slotRowDeleted() { KexiDataAwareObjectInterface::slotRowDeleted(); } + + //! Handles KexiTableViewData::rowInserted() signal to repaint when needed. + virtual void slotRowInserted(KexiTableItem *item, bool repaint) + { KexiDataAwareObjectInterface::slotRowInserted(item, repaint); } + + //! Like above, not db-aware version + virtual void slotRowInserted(KexiTableItem *item, uint row, bool repaint) + { KexiDataAwareObjectInterface::slotRowInserted(item, row, repaint); } + + /*! Handles verticalScrollBar()'s valueChanged(int) signal. + Called when vscrollbar's value has been changed. */ + virtual void vScrollBarValueChanged(int v) { KexiDataAwareObjectInterface::vScrollBarValueChanged(v); } + + /*! Handles sliderReleased() signal of the verticalScrollBar(). Used to hide the "row number" tooltip. */ + virtual void vScrollBarSliderReleased() { KexiDataAwareObjectInterface::vScrollBarSliderReleased(); } + + /*! Handles timeout() signal of the m_scrollBarTipTimer. If the tooltip is visible, + m_scrollBarTipTimerCnt is set to 0 and m_scrollBarTipTimerCnt is restarted; + else the m_scrollBarTipTimerCnt is just set to 0.*/ + virtual void scrollBarTipTimeout() { KexiDataAwareObjectInterface::scrollBarTipTimeout(); } + +protected: + /*! Reimplementation for KexiDataAwareObjectInterface + Initializes data contents (resizes it, sets cursor at 1st row). + Called on setData(). Also called once on show event after + reloadRequested() signal was received from KexiTableViewData object. */ + virtual void initDataContents(); + + /*! Implementation for KexiDataAwareObjectInterface. + Updates widget's contents size using QScrollView::resizeContents() + depending on tableSize(). */ + virtual void updateWidgetContentsSize(); + + /*! Reimplementation for KexiDataAwareObjectInterface */ + virtual void clearVariables(); + + /*! Implementation for KexiDataAwareObjectInterface */ + virtual int currentLocalSortingOrder() const; + + /*! Implementation for KexiDataAwareObjectInterface */ + virtual int currentLocalSortColumn() const; + + /*! Implementation for KexiDataAwareObjectInterface */ + virtual void setLocalSortingOrder(int col, int order); + + /*! Implementation for KexiDataAwareObjectInterface */ + virtual void updateGUIAfterSorting(); + + /*! Implementation for KexiDataAwareObjectInterface */ + virtual void updateWidgetScrollBars() { updateScrollBars(); } + +// /*! Implementation for KexiDataAwareObjectInterface */ +// virtual void emitSortedColumnChanged(int col) { emit sortedColumnChanged(col); } + +// /*! Implementation for KexiDataAwareObjectInterface */ +// virtual void emitRowEditTerminated(int row) { emit rowEditTerminated(row); } + + /*! Implementation for KexiDataAwareObjectInterface. + Adds another section within the horizontal header. */ + virtual void addHeaderColumn(const QString& caption, const QString& description, + const QIconSet& icon, int size); + + /*! @internal \return true if the row defined by \a item has default + value at column \a col. If this is the case and \a value is not NULL, + *value is set to the default value. */ + bool isDefaultValueDisplayed(KexiTableItem *item, int col, QVariant* value = 0); + + //! painting and layout + void drawContents(QPainter *p, int cx, int cy, int cw, int ch); + void createBuffer(int width, int height); + void paintCell(QPainter* p, KexiTableItem *item, int col, int row, const QRect &cr, bool print=false); + void paintEmptyArea(QPainter *p, int cx, int cy, int cw, int ch); + void updateGeometries(); + + QPoint contentsToViewport2( const QPoint &p ); + void contentsToViewport2( int x, int y, int& vx, int& vy ); + QPoint viewportToContents2( const QPoint& vp ); + + // event handling + virtual void contentsMousePressEvent(QMouseEvent* e); + virtual void contentsMouseReleaseEvent(QMouseEvent* e); + //! @internal called by contentsMouseOrEvent() contentsMouseReleaseEvent() to move cursor + bool handleContentsMousePressOrRelease(QMouseEvent* e, bool release); + virtual void contentsMouseMoveEvent(QMouseEvent* e); + virtual void contentsMouseDoubleClickEvent(QMouseEvent* e); + virtual void keyPressEvent(QKeyEvent* e); + virtual void focusInEvent(QFocusEvent* e); + virtual void focusOutEvent(QFocusEvent* e); + virtual void resizeEvent(QResizeEvent* e); + virtual void viewportResizeEvent(QResizeEvent *e); + virtual void showEvent(QShowEvent *e); + virtual void contentsDragMoveEvent(QDragMoveEvent *e); + virtual void contentsDropEvent(QDropEvent *e); + virtual void viewportDragLeaveEvent(QDragLeaveEvent *e); + virtual void paletteChange( const QPalette &oldPalette ); + + /*! Implementation for KexiDataAwareObjectInterface */ + virtual KexiDataItemInterface *editor( int col, bool ignoreMissingEditor = false ); + + inline KexiTableEdit *tableEditorWidget( int col, bool ignoreMissingEditor = false ) + { return dynamic_cast<KexiTableEdit*>( editor( col, ignoreMissingEditor ) ); } + + /*! Implementation for KexiDataAwareObjectInterface */ + virtual void editorShowFocus( int row, int col ); + + //! Creates editors and shows it, what usually means the beginning of a cell editing + virtual void createEditor(int row, int col, const QString& addText = QString::null, + bool removeOld = false); + + bool focusNextPrevChild(bool next); + + /*! Used in key event: \return true if event \a e should execute action \a action_name. + Action shortcuts defined by shortCutPressed() are reused, if present, and if \a e matches + given action's shortcut - false is returned (beause action is already performed at main + window's level). + */ + bool shortCutPressed( QKeyEvent *e, const QCString &action_name ); + +#if 0 //we have now KexiActionProxy + /*! Updates visibility/accesibility of popup menu items, + returns false if no items are visible after update. */ + bool updateContextMenu(); +#endif + + /*! Shows context menu at \a pos for selected cell + if menu is configured, + else: contextMenuRequested() signal is emmited. + Method used in contentsMousePressEvent() (for right button) + and keyPressEvent() for Qt::Key_Menu key. + If \a pos is QPoint(-1,-1) (the default), menu is positioned below the current cell. + */ + void showContextMenu( const QPoint& pos = QPoint(-1,-1) ); + + /*! internal */ + inline void paintRow(KexiTableItem *item, + QPainter *pb, int r, int rowp, int cx, int cy, + int colfirst, int collast, int maxwc); + + virtual void setHBarGeometry( QScrollBar & hbar, int x, int y, int w, int h ); + + //! Setups navigator widget + void setupNavigator(); + + //! internal, to determine valid row number when navigator text changed + int validRowNumber(const QString& text); + + /*! Reimplementation for KexiDataAwareObjectInterface + (viewport()->setFocus() is just added) */ + virtual void removeEditor(); + + //! Internal: updated sched fonts for painting. + void updateFonts(bool repaint = false); + + /*! @internal Changes bottom margin settings, in pixels. + At this time, it's used by KexiComboBoxPopup to decrease margin for popup's table. */ + void setBottomMarginInternal(int pixels); + + virtual void updateWidgetContents() { update(); } + + //! for navigator + virtual void moveToRecordRequested(uint r); + virtual void moveToLastRecordRequested(); + virtual void moveToPreviousRecordRequested(); + virtual void moveToNextRecordRequested(); + virtual void moveToFirstRecordRequested(); + virtual void addNewRecordRequested() { KexiDataAwareObjectInterface::addNewRecordRequested(); } + + //! Copy current selection to a clipboard (e.g. cell) + virtual void copySelection(); + + //! Cut current selection to a clipboard (e.g. cell) + virtual void cutSelection(); + + //! Paste current clipboard contents (e.g. to a cell) + virtual void paste(); + + /*! Used in KexiDataAwareObjectInterface::slotRowDeleted() + to repaint tow \a row and all visible below. */ + virtual void updateAllVisibleRowsBelow(int row); + + void updateAfterCancelRowEdit(); + void updateAfterAcceptRowEdit(); + + /*! Sets \a cellValue if there is a lookup value for the cell \a item. + Used in KexiTableView::paintCell() and KexiTableViewCellToolTip::maybeTip() + \return true is \a cellValue has been found. */ + bool getVisibleLookupValue(QVariant& cellValue, KexiTableEdit *edit, + KexiTableItem *item, KexiTableViewColumn *tvcol) const; + +// //! Called to repaint contents after a row is deleted. +// void repaintAfterDelete(); + + KexiTableViewPrivate *d; + + class WhatsThis; + friend class KexiTableItem; + friend class WhatsThis; + friend class KexiTableViewCellToolTip; +}; + +#endif diff --git a/kexi/widget/tableview/kexitableview_p.cpp b/kexi/widget/tableview/kexitableview_p.cpp new file mode 100644 index 00000000..7cf774db --- /dev/null +++ b/kexi/widget/tableview/kexitableview_p.cpp @@ -0,0 +1,67 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Till Busch <till@bux.at> + Copyright (C) 2003 Lucijan Busch <lucijan@gmx.at> + Copyright (C) 2003 Daniel Molkentin <molkentin@kde.org> + Copyright (C) 2003 Joseph Wenninger <jowenn@kde.org> + Copyright (C) 2003-2004 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. + + Original Author: Till Busch <till@bux.at> + Original Project: buX (www.bux.at) +*/ + +#include "kexitableview_p.h" +#include "kexitableedit.h" + +#include <qlabel.h> + +#include <kglobalsettings.h> + + +KexiTableViewPrivate::KexiTableViewPrivate(KexiTableView* t) + : appearance(t) +{ + clearVariables(); + tv = t; + editOnDoubleClick = true; + pBufferPm = 0; + disableDrawContents = false; + navigatorEnabled = true; + contextMenuEnabled = true; + skipKeyPress = false; +//moved vScrollBarValueChanged_enabled = true; +//moved scrollbarToolTipsEnabled = true; +//moved scrollBarTipTimerCnt = 0; +//moved scrollBarTip = 0; + ensureCellVisibleOnShow = QPoint(-1,-1); + internal_bottomMargin = tv->horizontalScrollBar()->sizeHint().height()/2; + highlightedRow = -1; + moveCursorOnMouseRelease = false; + horizontalHeaderVisible = true; + recentCellWithToolTip = QPoint(-1,-1); +} + +KexiTableViewPrivate::~KexiTableViewPrivate() +{ + delete pBufferPm; +//moved delete scrollBarTip; +} + +void KexiTableViewPrivate::clearVariables() +{ + // Initialize variables +} diff --git a/kexi/widget/tableview/kexitableview_p.h b/kexi/widget/tableview/kexitableview_p.h new file mode 100644 index 00000000..58fe8574 --- /dev/null +++ b/kexi/widget/tableview/kexitableview_p.h @@ -0,0 +1,155 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Till Busch <till@bux.at> + Copyright (C) 2003 Lucijan Busch <lucijan@gmx.at> + Copyright (C) 2003 Daniel Molkentin <molkentin@kde.org> + Copyright (C) 2003 Joseph Wenninger <jowenn@kde.org> + Copyright (C) 2003-2006 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. + + Original Author: Till Busch <till@bux.at> + Original Project: buX (www.bux.at) +*/ + +#ifndef KEXITABLEVIEW_P_H +#define KEXITABLEVIEW_P_H + +#include "kexitableview.h" + +#include <kexidb/roweditbuffer.h> +#include <widget/utils/kexidisplayutils.h> + +#include <qevent.h> +#include <qtimer.h> +#include <qvalidator.h> +#include <qasciidict.h> + +#include <kpushbutton.h> +#include <ktoolbarbutton.h> +#include <klineedit.h> +#include <kpopupmenu.h> +#include <kaction.h> + +class KexiTableItem; +class KexiTableRM; +class KexiTableEdit; +class QLabel; +class KexiTableViewHeader; + +//! @short a dynamic tooltip for table view cells +/*! @internal */ +class KexiTableViewCellToolTip : public QToolTip +{ + public: + KexiTableViewCellToolTip( KexiTableView * tableView ); + virtual ~KexiTableViewCellToolTip(); + protected: + virtual void maybeTip( const QPoint & p ); + + KexiTableView *m_tableView; +}; + +/*! KexiTableView's internal structures + @internal */ +class KexiTableViewPrivate +{ + public: + + KexiTableViewPrivate(KexiTableView* t); + ~KexiTableViewPrivate(); + + void clearVariables(); + + KexiTableView *tv; + + //! editors: one for each column (indexed by KexiTableViewColumn) + QPtrDict<KexiTableEdit> editors; + + int rowHeight; + + QPixmap *pBufferPm; + QTimer *pUpdateTimer; + int menu_id_addRecord; + int menu_id_removeRecord; + +#if 0//(js) doesn't work! + QTimer *scrollTimer; +#endif + + KexiTableView::ScrollDirection scrollDirection; + + bool editOnDoubleClick : 1; + + bool needAutoScroll : 1; + + bool disableDrawContents : 1; + + /*! true if the navigation panel is enabled (visible) for the view. + True by default. */ + bool navigatorEnabled : 1; + + /*! true if the context menu is enabled (visible) for the view. + True by default. */ + bool contextMenuEnabled : 1; + + /*! used to force single skip keyPress event. */ + bool skipKeyPress : 1; + + /*! Needed because m_horizontalHeader->isVisible() is not always accurate. True by default. */ + bool horizontalHeaderVisible : 1; + + /*! true if cursor should be moved on mouse release evenr rather than mouse press + in handleContentsMousePressOrRelease(). + False by default. Used by KeixComboBoxPopup. */ + bool moveCursorOnMouseRelease : 1; + + KexiTableView::Appearance appearance; + + //! brushes, fonts + QBrush diagonalGrayPattern; + + //! Parameters for displaying autonumbers + KexiDisplayUtils::DisplayParameters autonumberSignDisplayParameters; + + //! Parameters for displaying default values + KexiDisplayUtils::DisplayParameters defaultValueDisplayParameters; + + //! Used by delayed mode of maximizeColumnsWidth() + QValueList<int> maximizeColumnsWidthOnShow; + + /*! Used for delayed call of ensureCellVisible() after show(). + It's equal to (-1,-1) if ensureCellVisible() shouldn't e called. */ + QPoint ensureCellVisibleOnShow; + + /*! @internal Changes bottom margin settings, in pixels. + At this time, it's used by KexiComboBoxPopup to decrease margin for popup's table. */ + int internal_bottomMargin; + + /*! Helper for "highlighted row" effect. */ + int highlightedRow; + + /*! Id of context menu key (cached). */ + int contextMenuKey; + + /*! Specifies currently displayed cell tooltip. + Value of QPoint(-1,-1) means "no tooltip". */ + QPoint recentCellWithToolTip; + + /*! Table cell tooltip */ + KexiTableViewCellToolTip *cellToolTip; +}; + +#endif diff --git a/kexi/widget/tableview/kexitableviewdata.cpp b/kexi/widget/tableview/kexitableviewdata.cpp new file mode 100644 index 00000000..62259db3 --- /dev/null +++ b/kexi/widget/tableview/kexitableviewdata.cpp @@ -0,0 +1,886 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Lucijan Busch <lucijan@gmx.at> + Copyright (C) 2003 Daniel Molkentin <molkentin@kde.org> + Copyright (C) 2003-2006 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. + + Original Author: Till Busch <till@bux.at> + Original Project: buX (www.bux.at) +*/ + +#include "kexitableviewdata.h" + +#include <kexiutils/validator.h> + +#include <kexidb/field.h> +#include <kexidb/queryschema.h> +#include <kexidb/roweditbuffer.h> +#include <kexidb/cursor.h> +#include <kexidb/utils.h> +#include <kexi.h> + +#include <kdebug.h> +#include <klocale.h> + +#include <qapplication.h> + +unsigned short KexiTableViewData::charTable[]= +{ + #include "chartable.txt" +}; + +KexiTableViewColumn::KexiTableViewColumn(KexiDB::Field& f, bool owner) +: columnInfo(0) +, visibleLookupColumnInfo(0) +, m_field(&f) +{ + isDBAware = false; + m_fieldOwned = owner; + m_captionAliasOrName = m_field->captionOrName(); + init(); +} + +KexiTableViewColumn::KexiTableViewColumn(const QString& name, KexiDB::Field::Type ctype, + uint cconst, + uint options, + uint length, uint precision, + QVariant defaultValue, + const QString& caption, const QString& description, uint width +) +: columnInfo(0) +, visibleLookupColumnInfo(0) +{ + m_field = new KexiDB::Field( + name, ctype, + cconst, + options, + length, precision, + defaultValue, + caption, description, width); + + isDBAware = false; + m_fieldOwned = true; + m_captionAliasOrName = m_field->captionOrName(); + init(); +} + +KexiTableViewColumn::KexiTableViewColumn(const QString& name, KexiDB::Field::Type ctype, + const QString& caption, const QString& description) +: columnInfo(0) +, visibleLookupColumnInfo(0) +{ + m_field = new KexiDB::Field( + name, ctype, + KexiDB::Field::NoConstraints, + KexiDB::Field::NoOptions, + 0, 0, + QVariant(), + caption, description); + + isDBAware = false; + m_fieldOwned = true; + m_captionAliasOrName = m_field->captionOrName(); + init(); +} + +// db-aware +KexiTableViewColumn::KexiTableViewColumn( + const KexiDB::QuerySchema &query, KexiDB::QueryColumnInfo& aColumnInfo, + KexiDB::QueryColumnInfo* aVisibleLookupColumnInfo) +: columnInfo(&aColumnInfo) +, visibleLookupColumnInfo(aVisibleLookupColumnInfo) +, m_field(aColumnInfo.field) +{ + isDBAware = true; + m_fieldOwned = false; + + //setup column's caption: + if (!columnInfo->field->caption().isEmpty()) { + m_captionAliasOrName = columnInfo->field->caption(); + } + else { + //reuse alias if available: + m_captionAliasOrName = columnInfo->alias; + //last hance: use field name + if (m_captionAliasOrName.isEmpty()) + m_captionAliasOrName = columnInfo->field->name(); + //todo: compute other auto-name? + } + init(); + //setup column's readonly flag: true, if + // - it's not from parent table's field, or + // - if the query itself is coming from read-only connection, or + // - if the query itself is stored (i.e. has connection) and lookup column is defined + const bool columnFromMasterTable = query.masterTable()==columnInfo->field->table(); + m_readOnly = !columnFromMasterTable + || (query.connection() && query.connection()->isReadOnly()); +// || (query.connection() && (query.connection()->isReadOnly() || visibleLookupColumnInfo)); +//! @todo 2.0: remove this when queries become editable ^^^^^^^^^^^^^^ +// kdDebug() << "KexiTableViewColumn: query.masterTable()==" +// << (query.masterTable() ? query.masterTable()->name() : "notable") << ", columnInfo->field->table()==" +// << (columnInfo->field->table() ? columnInfo->field->table()->name() : "notable") << endl; + +// m_visible = query.isFieldVisible(&f); +} + +KexiTableViewColumn::KexiTableViewColumn(bool) +: columnInfo(0) +, visibleLookupColumnInfo(0) +, m_field(0) +{ + isDBAware = false; + init(); +} + +KexiTableViewColumn::~KexiTableViewColumn() +{ + if (m_fieldOwned) + delete m_field; + setValidator( 0 ); + delete m_relatedData; +} + +void KexiTableViewColumn::init() +{ + m_relatedData = 0; + m_readOnly = false; + m_visible = true; + m_data = 0; + m_validator = 0; + m_relatedDataEditable = false; + m_headerTextVisible = true; +} + +void KexiTableViewColumn::setValidator( KexiUtils::Validator* v ) +{ + if (m_validator) {//remove old one + if (!m_validator->parent()) //destroy if has no parent + delete m_validator; + } + m_validator = v; +} + +void KexiTableViewColumn::setRelatedData(KexiTableViewData *data) +{ + if (isDBAware) + return; + if (m_relatedData) + delete m_relatedData; + m_relatedData = 0; + if (!data) + return; + //find a primary key + KexiTableViewColumn::ListIterator it( data->columns ); + for (int id = 0;it.current();++it, id++) { + if (it.current()->field()->isPrimaryKey()) { + //found, remember + m_relatedDataPKeyID = id; + m_relatedData = data; + return; + } + } +} + +void KexiTableViewColumn::setRelatedDataEditable(bool set) +{ + m_relatedDataEditable = set; +} + +bool KexiTableViewColumn::isReadOnly() const +{ + return m_readOnly || (m_data && m_data->isReadOnly()); +} + +bool KexiTableViewColumn::acceptsFirstChar(const QChar& ch) const +{ + // the field we're looking at can be related to "visible lookup column" + // if lookup column is present + KexiDB::Field *visibleField = visibleLookupColumnInfo + ? visibleLookupColumnInfo->field : m_field; + if (visibleField->isNumericType()) { + if (ch=='.' || ch==',') + return visibleField->isFPNumericType(); + if (ch=='-') + return !visibleField->isUnsigned(); + if (ch=='+' || (ch>='0' && ch<='9')) + return true; + return false; + } + + switch (visibleField->type()) { + case KexiDB::Field::Boolean: + return false; + case KexiDB::Field::Date: + case KexiDB::Field::DateTime: + case KexiDB::Field::Time: + return ch>='0' && ch<='9'; + default:; + } + return true; +} + + +//------------------------------------------------------ + +KexiTableViewData::KexiTableViewData() + : QObject() + , KexiTableViewDataBase() +{ + init(); +} + +// db-aware ctor +KexiTableViewData::KexiTableViewData(KexiDB::Cursor *c) + : QObject() + , KexiTableViewDataBase() +{ + init(); + m_cursor = c; + m_containsROWIDInfo = m_cursor->containsROWIDInfo(); + if (m_cursor && m_cursor->query()) { + const KexiDB::QuerySchema::FieldsExpandedOptions fieldsExpandedOptions + = m_containsROWIDInfo ? KexiDB::QuerySchema::WithInternalFieldsAndRowID + : KexiDB::QuerySchema::WithInternalFields; + m_itemSize = m_cursor->query()->fieldsExpanded( fieldsExpandedOptions ).count(); + } + else + m_itemSize = columns.count()+(m_containsROWIDInfo?1:0); + + // Allocate KexiTableViewColumn objects for each visible query column + const KexiDB::QueryColumnInfo::Vector fields = m_cursor->query()->fieldsExpanded(); + const uint fieldsCount = fields.count(); + for (uint i=0;i < fieldsCount;i++) { + KexiDB::QueryColumnInfo *ci = fields[i]; + if (ci->visible) { + KexiDB::QueryColumnInfo *visibleLookupColumnInfo = 0; + if (ci->indexForVisibleLookupValue() != -1) { + //Lookup field is defined + visibleLookupColumnInfo = m_cursor->query()->expandedOrInternalField( ci->indexForVisibleLookupValue() ); + /* not needed + if (visibleLookupColumnInfo) { + // 2. Create a KexiTableViewData object for each found lookup field + }*/ + } + KexiTableViewColumn* col = new KexiTableViewColumn(*m_cursor->query(), *ci, visibleLookupColumnInfo); + addColumn( col ); + } + } +} + +KexiTableViewData::KexiTableViewData( + const QValueList<QVariant> &keys, const QValueList<QVariant> &values, + KexiDB::Field::Type keyType, KexiDB::Field::Type valueType) + : QObject() + , KexiTableViewDataBase() +{ + init(keys, values, keyType, valueType); +} + +KexiTableViewData::KexiTableViewData( + KexiDB::Field::Type keyType, KexiDB::Field::Type valueType) +{ + const QValueList<QVariant> empty; + init(empty, empty, keyType, valueType); +} + +KexiTableViewData::~KexiTableViewData() +{ + emit destroying(); + clearInternal(); +} + +void KexiTableViewData::init( + const QValueList<QVariant> &keys, const QValueList<QVariant> &values, + KexiDB::Field::Type keyType, KexiDB::Field::Type valueType) +{ + init(); + KexiDB::Field *keyField = new KexiDB::Field("key", keyType); + keyField->setPrimaryKey(true); + KexiTableViewColumn *keyColumn = new KexiTableViewColumn(*keyField, true); + keyColumn->setVisible(false); + addColumn(keyColumn); + + KexiDB::Field *valueField = new KexiDB::Field("value", valueType); + KexiTableViewColumn *valueColumn = new KexiTableViewColumn(*valueField, true); + addColumn(valueColumn); + + uint cnt = QMIN(keys.count(), values.count()); + QValueList<QVariant>::ConstIterator it_keys = keys.constBegin(); + QValueList<QVariant>::ConstIterator it_values = values.constBegin(); + for (;cnt>0;++it_keys, ++it_values, cnt--) { + KexiTableItem *item = new KexiTableItem(2); + (*item)[0] = (*it_keys); + (*item)[1] = (*it_values); + append( item ); + } +} + +void KexiTableViewData::init() +{ + m_sortedColumn = 0; + m_realSortedColumn = 0; +// m_order = 1; + m_order = 0; + m_type = 1; + m_pRowEditBuffer = 0; + m_cursor = 0; + m_readOnly = false; + m_insertingEnabled = true; + + setAutoDelete(true); + columns.setAutoDelete(true); + m_visibleColumnsCount=0; + m_visibleColumnsIDs.resize(100); + m_globalColumnsIDs.resize(100); + + m_autoIncrementedColumn = -2; + m_containsROWIDInfo = false; + m_itemSize = 0; +} + +void KexiTableViewData::deleteLater() +{ + m_cursor = 0; + QObject::deleteLater(); +} + +void KexiTableViewData::addColumn( KexiTableViewColumn* col ) +{ +// if (!col->isDBAware) { +// if (!m_simpleColumnsByName) +// m_simpleColumnsByName = new QDict<KexiTableViewColumn>(101); +// m_simpleColumnsByName->insert(col->caption,col);//for faster lookup +// } + columns.append( col ); + col->m_data = this; + if (m_globalColumnsIDs.size() < columns.count()) {//sanity + m_globalColumnsIDs.resize( m_globalColumnsIDs.size()*2 ); + } + if (col->visible()) { + m_visibleColumnsCount++; + if (m_visibleColumnsIDs.size() < m_visibleColumnsCount) {//sanity + m_visibleColumnsIDs.resize( m_visibleColumnsIDs.size()*2 ); + } + m_visibleColumnsIDs[ columns.count()-1 ] = m_visibleColumnsCount-1; + m_globalColumnsIDs[ m_visibleColumnsCount-1 ] = columns.count()-1; + } + else { + m_visibleColumnsIDs[ columns.count()-1 ] = -1; + } + m_autoIncrementedColumn = -2; //clear cache; + if (!m_cursor || !m_cursor->query()) + m_itemSize = columns.count()+(m_containsROWIDInfo?1:0); +} + +QString KexiTableViewData::dbTableName() const +{ + if (m_cursor && m_cursor->query() && m_cursor->query()->masterTable()) + return m_cursor->query()->masterTable()->name(); + return QString::null; +} + +void KexiTableViewData::setSorting(int column, bool ascending) +{ + if (column>=0 && column<(int)columns.count()) { + m_order = (ascending ? 1 : -1); + } + else { + m_order = 0; + m_sortedColumn = -1; + m_realSortedColumn = -1; + return; + } + // find proper column information for sorting (lookup column points to alternate column with visible data) + const KexiTableViewColumn *tvcol = columns.at(column); + KexiDB::QueryColumnInfo* visibleLookupColumnInfo = tvcol->visibleLookupColumnInfo; + const KexiDB::Field *field = visibleLookupColumnInfo ? visibleLookupColumnInfo->field : tvcol->field(); + m_sortedColumn = column; + m_realSortedColumn = tvcol->columnInfo->indexForVisibleLookupValue()!=-1 + ? tvcol->columnInfo->indexForVisibleLookupValue() : m_sortedColumn; + + // setup compare function + const int t = field->type(); + if (field->isTextType()) + cmpFunc = &KexiTableViewData::cmpStr; + else if (KexiDB::Field::isFPNumericType(t)) + cmpFunc = &KexiTableViewData::cmpDouble; + else if (t==KexiDB::Field::BigInteger) { + if (field->isUnsigned()) + cmpFunc = &KexiTableViewData::cmpULongLong; + else + cmpFunc = &KexiTableViewData::cmpLongLong; + } + else if (t == KexiDB::Field::Integer && field->isUnsigned()) + cmpFunc = &KexiTableViewData::cmpUInt; + else if (t == KexiDB::Field::Boolean || KexiDB::Field::isNumericType(t)) + cmpFunc = &KexiTableViewData::cmpInt; //other integers + else if (t == KexiDB::Field::Date) + cmpFunc = &KexiTableViewData::cmpDate; + else if (t == KexiDB::Field::Time) + cmpFunc = &KexiTableViewData::cmpTime; + else if (t == KexiDB::Field::DateTime) + cmpFunc = &KexiTableViewData::cmpDateTime; + else if (t == KexiDB::Field::BLOB) +//! TODO allow users to define BLOB sorting function? + cmpFunc = &KexiTableViewData::cmpBLOB; + else + cmpFunc = &KexiTableViewData::cmpStr; //anything else +} + +int KexiTableViewData::compareItems(Item item1, Item item2) +{ + return ((this->*cmpFunc) (item1, item2)); +} + +//! compare NULLs : NULL is smaller than everything +#define CMP_NULLS(item1, item2) \ + m_leftTmp = ((KexiTableItem *)item1)->at(m_realSortedColumn); \ + if (m_leftTmp.isNull()) \ + return -m_order; \ + m_rightTmp = ((KexiTableItem *)item2)->at(m_realSortedColumn); \ + if (m_rightTmp.isNull()) \ + return m_order + +#define CAST_AND_COMPARE(casting, item1, item2) \ + CMP_NULLS(item1, item2); \ + if (m_leftTmp.casting() < m_rightTmp.casting()) \ + return -m_order; \ + if (m_leftTmp.casting() > m_rightTmp.casting()) \ + return m_order; \ + return 0 + +int KexiTableViewData::cmpInt(Item item1, Item item2) +{ + CAST_AND_COMPARE(toInt, item1, item2); +} + +int KexiTableViewData::cmpUInt(Item item1, Item item2) +{ + CAST_AND_COMPARE(toUInt, item1, item2); +} + +int KexiTableViewData::cmpLongLong(Item item1, Item item2) +{ + CAST_AND_COMPARE(toLongLong, item1, item2); +} + +int KexiTableViewData::cmpULongLong(Item item1, Item item2) +{ + CAST_AND_COMPARE(toULongLong, item1, item2); +} + +int KexiTableViewData::cmpDouble(Item item1, Item item2) +{ + CAST_AND_COMPARE(toDouble, item1, item2); +} + +int KexiTableViewData::cmpDate(Item item1, Item item2) +{ + CAST_AND_COMPARE(toDate, item1, item2); +} + +int KexiTableViewData::cmpDateTime(Item item1, Item item2) +{ + CAST_AND_COMPARE(toDateTime, item1, item2); +} + +int KexiTableViewData::cmpTime(Item item1, Item item2) +{ + CAST_AND_COMPARE(toDate, item1, item2); +} + +int KexiTableViewData::cmpStr(Item item1, Item item2) +{ + CMP_NULLS(item1, item2); + const QString &as = m_leftTmp.toString(); + const QString &bs = m_rightTmp.toString(); + + const QChar *a = as.unicode(); + const QChar *b = bs.unicode(); + + if ( a == b ) + return 0; + if ( a == 0 ) + return -1; + if ( b == 0 ) + return 1; + + unsigned short au; + unsigned short bu; + + int l=QMIN(as.length(),bs.length()); + + au = a->unicode(); + bu = b->unicode(); + au = (au <= 0x17e ? charTable[au] : 0xffff); + bu = (bu <= 0x17e ? charTable[bu] : 0xffff); + + while (l-- && au == bu) + { + a++,b++; + au = a->unicode(); + bu = b->unicode(); + au = (au <= 0x17e ? charTable[au] : 0xffff); + bu = (bu <= 0x17e ? charTable[bu] : 0xffff); + } + + if ( l==-1 ) + return m_order*(as.length()-bs.length()); + + return m_order*(au-bu); +} + +int KexiTableViewData::cmpBLOB(Item item1, Item item2) +{ + CMP_NULLS(item1, item2); + return m_leftTmp.toByteArray().size() - m_rightTmp.toByteArray().size(); +} + +void KexiTableViewData::setReadOnly(bool set) +{ + if (m_readOnly == set) + return; + m_readOnly = set; + if (m_readOnly) + setInsertingEnabled(false); +} + +void KexiTableViewData::setInsertingEnabled(bool set) +{ + if (m_insertingEnabled == set) + return; + m_insertingEnabled = set; + if (m_insertingEnabled) + setReadOnly(false); +} + +void KexiTableViewData::clearRowEditBuffer() +{ + //init row edit buffer + if (!m_pRowEditBuffer) + m_pRowEditBuffer = new KexiDB::RowEditBuffer(isDBAware()); + else + m_pRowEditBuffer->clear(); +} + +bool KexiTableViewData::updateRowEditBufferRef(KexiTableItem *item, + int colnum, KexiTableViewColumn* col, QVariant& newval, bool allowSignals, + QVariant *visibleValueForLookupField) +{ + m_result.clear(); + if (allowSignals) + emit aboutToChangeCell(item, colnum, newval, &m_result); + if (!m_result.success) + return false; + + kdDebug() << "KexiTableViewData::updateRowEditBufferRef() column #" + << colnum << " = " << newval.toString() << endl; + if (!col) { + kdWarning() << "KexiTableViewData::updateRowEditBufferRef(): column #" + << colnum << " not found! col==0" << endl; + return false; + } + if (!m_pRowEditBuffer) + m_pRowEditBuffer = new KexiDB::RowEditBuffer(isDBAware()); + if (m_pRowEditBuffer->isDBAware()) { + if (!(col->columnInfo)) { + kdWarning() << "KexiTableViewData::updateRowEditBufferRef(): column #" + << colnum << " not found!" << endl; + return false; + } + m_pRowEditBuffer->insert( *col->columnInfo, newval); + + if (col->visibleLookupColumnInfo && visibleValueForLookupField) { + //this is value for lookup table: update visible value as well + m_pRowEditBuffer->insert( *col->visibleLookupColumnInfo, *visibleValueForLookupField); + } + return true; + } + if (!(col->field())) { + kdDebug() << "KexiTableViewData::updateRowEditBufferRef(): column #" << colnum<<" not found!" << endl; + return false; + } + //not db-aware: + const QString colname = col->field()->name(); + if (colname.isEmpty()) { + kdDebug() << "KexiTableViewData::updateRowEditBufferRef(): column #" << colnum<<" not found!" << endl; + return false; + } + m_pRowEditBuffer->insert(colname, newval); + return true; +} + +//get a new value (if present in the buffer), or the old one, otherwise +//(taken here for optimization) +#define GET_VALUE if (!val) { \ + val = m_cursor \ + ? m_pRowEditBuffer->at( *it_f.current()->columnInfo, true /* useDefaultValueIfPossible */ ) \ + : m_pRowEditBuffer->at( *f ); \ + if (!val) \ + val = &(*it_r); /* get old value */ \ + } + +//! @todo if there're multiple views for this data, we need multiple buffers! +bool KexiTableViewData::saveRow(KexiTableItem& item, bool insert, bool repaint) +{ + if (!m_pRowEditBuffer) + return true; //nothing to do + + //check constraints: + //-check if every NOT NULL and NOT EMPTY field is filled + KexiTableViewColumn::ListIterator it_f(columns); + KexiDB::RowData::ConstIterator it_r = item.constBegin(); + int col = 0; + const QVariant *val; + for (;it_f.current() && it_r!=item.constEnd();++it_f,++it_r,col++) { + KexiDB::Field *f = it_f.current()->field(); + val = 0; + if (f->isNotNull()) { + GET_VALUE; + //check it + if (val->isNull() && !f->isAutoIncrement()) { + //NOT NULL violated + m_result.msg = i18n("\"%1\" column requires a value to be entered.") + .arg(f->captionOrName()) + "\n\n" + Kexi::msgYouCanImproveData(); + m_result.desc = i18n("The column's constraint is declared as NOT NULL."); + m_result.column = col; + return false; + } + } + if (f->isNotEmpty()) { + GET_VALUE; + if (!f->isAutoIncrement() && (val->isNull() || KexiDB::isEmptyValue( f, *val ))) { + //NOT EMPTY violated + m_result.msg = i18n("\"%1\" column requires a value to be entered.") + .arg(f->captionOrName()) + "\n\n" + Kexi::msgYouCanImproveData(); + m_result.desc = i18n("The column's constraint is declared as NOT EMPTY."); + m_result.column = col; + return false; + } + } + } + + if (m_cursor) {//db-aware + if (insert) { + if (!m_cursor->insertRow( static_cast<KexiDB::RowData&>(item), *m_pRowEditBuffer, + m_containsROWIDInfo/*also retrieve ROWID*/ )) + { + m_result.msg = i18n("Row inserting failed.") + "\n\n" + + Kexi::msgYouCanImproveData(); + KexiDB::getHTMLErrorMesage(m_cursor, &m_result); + +/* if (desc) + *desc = +js: TODO: use KexiMainWindowImpl::showErrorMessage(const QString &title, KexiDB::Object *obj) + after it will be moved somewhere to kexidb (this will require moving other + showErrorMessage() methods from KexiMainWindowImpl to libkexiutils....) + then: just call: *desc = KexiDB::errorMessage(m_cursor); +*/ + return false; + } + } + else { // row updating +// if (m_containsROWIDInfo) +// ROWID = item[columns.count()].toULongLong(); + if (!m_cursor->updateRow( static_cast<KexiDB::RowData&>(item), *m_pRowEditBuffer, + m_containsROWIDInfo/*use ROWID*/)) + { + m_result.msg = i18n("Row changing failed.") + "\n\n" + Kexi::msgYouCanImproveData(); +//! @todo set m_result.column if possible + KexiDB::getHTMLErrorMesage(m_cursor, m_result.desc); + return false; + } + } + } + else {//not db-aware version + KexiDB::RowEditBuffer::SimpleMap b = m_pRowEditBuffer->simpleBuffer(); + for (KexiDB::RowEditBuffer::SimpleMap::ConstIterator it = b.constBegin();it!=b.constEnd();++it) { + uint i=0; + for (KexiTableViewColumn::ListIterator it2(columns);it2.current();++it2, i++) { + if (it2.current()->field()->name()==it.key()) { + kdDebug() << it2.current()->field()->name()<< ": "<<item[i].toString()<<" -> "<<it.data().toString()<<endl; + item[i] = it.data(); + } + } + } + } + + m_pRowEditBuffer->clear(); + + if (repaint) + emit rowRepaintRequested(item); + return true; +} + +bool KexiTableViewData::saveRowChanges(KexiTableItem& item, bool repaint) +{ + kdDebug() << "KexiTableViewData::saveRowChanges()..." << endl; + m_result.clear(); + emit aboutToUpdateRow(&item, m_pRowEditBuffer, &m_result); + if (!m_result.success) + return false; + + if (saveRow(item, false /*update*/, repaint)) { + emit rowUpdated(&item); + return true; + } + return false; +} + +bool KexiTableViewData::saveNewRow(KexiTableItem& item, bool repaint) +{ + kdDebug() << "KexiTableViewData::saveNewRow()..." << endl; + m_result.clear(); + emit aboutToInsertRow(&item, &m_result, repaint); + if (!m_result.success) + return false; + + if (saveRow(item, true /*insert*/, repaint)) { + emit rowInserted(&item, repaint); + return true; + } + return false; +} + +bool KexiTableViewData::deleteRow(KexiTableItem& item, bool repaint) +{ + m_result.clear(); + emit aboutToDeleteRow(item, &m_result, repaint); + if (!m_result.success) + return false; + + if (m_cursor) {//db-aware + m_result.success = false; + if (!m_cursor->deleteRow( static_cast<KexiDB::RowData&>(item), m_containsROWIDInfo/*use ROWID*/ )) { + m_result.msg = i18n("Row deleting failed."); +/*js: TODO: use KexiDB::errorMessage() for description (desc) as in KexiTableViewData::saveRow() */ + KexiDB::getHTMLErrorMesage(m_cursor, &m_result); + m_result.success = false; + return false; + } + } + + if (!removeRef(&item)) { + //aah - this shouldn't be! + kdWarning() << "KexiTableViewData::deleteRow(): !removeRef() - IMPL. ERROR?" << endl; + m_result.success = false; + return false; + } + emit rowDeleted(); + return true; +} + +void KexiTableViewData::deleteRows( const QValueList<int> &rowsToDelete, bool repaint ) +{ + Q_UNUSED( repaint ); + + if (rowsToDelete.isEmpty()) + return; + int last_r=0; + first(); + for (QValueList<int>::ConstIterator r_it = rowsToDelete.constBegin(); r_it!=rowsToDelete.constEnd(); ++r_it) { + for (; last_r<(*r_it); last_r++) { + next(); + } + remove(); + last_r++; + } +//DON'T CLEAR BECAUSE KexiTableViewPropertyBuffer will clear BUFFERS! +//--> emit reloadRequested(); //! \todo more effective? + emit rowsDeleted( rowsToDelete ); +} + +void KexiTableViewData::insertRow(KexiTableItem& item, uint index, bool repaint) +{ + if (!insert( index = QMIN(index, count()), &item )) + return; + emit rowInserted(&item, index, repaint); +} + +void KexiTableViewData::clearInternal() +{ + clearRowEditBuffer(); +// qApp->processEvents( 1 ); +//TODO: this is time consuming: find better data model +// KexiTableViewDataBase::clear(); + const uint c = count(); + for (uint i=0; i<c; i++) { + removeLast(); +#ifndef KEXI_NO_PROCESS_EVENTS + if (i % 1000 == 0) + qApp->processEvents( 1 ); +#endif + } +} + +bool KexiTableViewData::deleteAllRows(bool repaint) +{ + clearInternal(); + + bool res = true; + if (m_cursor) { + //db-aware + res = m_cursor->deleteAllRows(); + } + + if (repaint) + emit reloadRequested(); + return res; +} + +int KexiTableViewData::autoIncrementedColumn() +{ + if (m_autoIncrementedColumn==-2) { + //find such a column + m_autoIncrementedColumn = 0; + KexiTableViewColumn::ListIterator it(columns); + for (; it.current(); ++it, m_autoIncrementedColumn++) { + if (it.current()->field()->isAutoIncrement()) + break; + } + if (!it.current()) + m_autoIncrementedColumn = -1; + } + return m_autoIncrementedColumn; +} + +void KexiTableViewData::preloadAllRows() +{ + if (!m_cursor) + return; + + //const uint fcount = m_cursor->fieldCount() + (m_containsROWIDInfo ? 1 : 0); + m_cursor->moveFirst(); + for (int i=0;!m_cursor->eof();i++) { + KexiTableItem *item = new KexiTableItem(0); + m_cursor->storeCurrentRow(*item); +// item->debug(); + append( item ); + m_cursor->moveNext(); +#ifndef KEXI_NO_PROCESS_EVENTS + if ((i % 1000) == 0) + qApp->processEvents( 1 ); +#endif + } +} + +bool KexiTableViewData::isReadOnly() const +{ + return m_readOnly || (m_cursor && m_cursor->connection()->isReadOnly()); +} + +#include "kexitableviewdata.moc" diff --git a/kexi/widget/tableview/kexitableviewdata.h b/kexi/widget/tableview/kexitableviewdata.h new file mode 100644 index 00000000..970d1d23 --- /dev/null +++ b/kexi/widget/tableview/kexitableviewdata.h @@ -0,0 +1,540 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Lucijan Busch <lucijan@gmx.at> + Copyright (C) 2003 Daniel Molkentin <molkentin@kde.org> + Copyright (C) 2003-2006 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. + + Original Author: Till Busch <till@bux.at> + Original Project: buX (www.bux.at) +*/ + +#ifndef KEXITABLEVIEWDATA_H +#define KEXITABLEVIEWDATA_H + +#include <qptrlist.h> +#include <qvariant.h> +#include <qvaluevector.h> +#include <qstring.h> +#include <qobject.h> + +#include "kexitableitem.h" + +#include <kexidb/error.h> + +namespace KexiDB { +class Field; +class QuerySchema; +class RowEditBuffer; +class Cursor; +} + +namespace KexiUtils { +class Validator; +} +class KexiTableViewData; + + +/*! Single column definition. */ +class KEXIDATATABLE_EXPORT KexiTableViewColumn { + public: + typedef QPtrList<KexiTableViewColumn> List; + typedef QPtrListIterator<KexiTableViewColumn> ListIterator; + + /*! Not db-aware ctor. if \a owner is true, the field \a will be owned by this column, + so you shouldn't care about destroying this field. */ + KexiTableViewColumn(KexiDB::Field& f, bool owner = false); + + /*! Not db-aware, convenience ctor, like above. The field is created using specified parameters that are + equal to these accepted by KexiDB::Field ctor. The column will be the owner + of this automatically generated field. + */ + KexiTableViewColumn(const QString& name, KexiDB::Field::Type ctype, + uint cconst = KexiDB::Field::NoConstraints, + uint options = KexiDB::Field::NoOptions, + uint length=0, uint precision=0, + QVariant defaultValue=QVariant(), + const QString& caption = QString::null, + const QString& description = QString::null, + uint width = 0); + + /*! Not db-aware, convenience ctor, simplified version of the above. */ + KexiTableViewColumn(const QString& name, KexiDB::Field::Type ctype, const QString& caption, + const QString& description = QString::null); + + //! Db-aware version. + KexiTableViewColumn(const KexiDB::QuerySchema &query, KexiDB::QueryColumnInfo& aColumnInfo, + KexiDB::QueryColumnInfo* aVisibleLookupColumnInfo = 0); + + virtual ~KexiTableViewColumn(); + + virtual bool acceptsFirstChar(const QChar& ch) const; + + /*! \return true if the column is read-only + For db-aware column this can depend on whether the column + is in parent table of this query. \sa setReadOnly() */ + bool isReadOnly() const; + +//TODO: synchronize this with table view: + //! forces readOnly flag to be set to \a ro + inline void setReadOnly(bool ro) { m_readOnly=ro; } + + //! Column visibility. By default column is visible. + inline bool visible() const { return columnInfo ? columnInfo->visible : m_visible; } + + //! Changes column visibility. + inline void setVisible(bool v) { + if (columnInfo) + columnInfo->visible = v; + m_visible=v; + } + + /*! Sets icon for displaying in the caption area (header). */ + void setIcon(const QIconSet& icon) { m_icon = icon; } + + /*! \return bame of icon displayed in the caption area (header). */ + QIconSet icon() const { return m_icon; } + + /*! If \a visible is true, caption has to be displayed in the column's header, + (or field's name if caption is empty. True by default. */ + void setHeaderTextVisible(bool visible) { m_headerTextVisible = visible; } + + /*! \return true if caption has to be displayed in the column's header, + (or field's name if caption is empty. */ + bool isHeaderTextVisible() const { return m_headerTextVisible; } + + /*! \return whatever is available: + - field's caption + - or field's alias (from query) + - or finally - field's name */ + inline QString captionAliasOrName() const { return m_captionAliasOrName; } + + /*! Assigns validator \a v for this column. + If the validator has no parent object, it will be owned by the column, + so you shouldn't care about destroying it. */ + void setValidator( KexiUtils::Validator* v ); + + //! \return validator assigned for this column of 0 if there is no validator assigned. + inline KexiUtils::Validator* validator() const { return m_validator; } + + /*! For not-db-aware data only: + Sets related data \a data for this column, what defines simple one-field, + one-to-many relationship between this column and the primary key in \a data. + The relationship will be used to generate a popup editor instead of just regular editor. + This assignment has no result if \a data has no primary key defined. + \a data is owned, so is will be destroyed when needed. It is also destroyed + when another data (or NULL) is set for the same column. */ + void setRelatedData(KexiTableViewData *data); + + /*! For not-db-aware data only: + Related data \a data for this column, what defines simple one-field. + NULL by default. \sa setRelatedData() */ + inline KexiTableViewData *relatedData() const { return m_relatedData; } + + /*! \return field for this column. + For db-aware information is taken from \a columnInfo member. */ + inline KexiDB::Field* field() const { return m_field; } + + /*! Only usable if related data is set (ie. this is for combo boxes). + Sets 'editable' flag for this column, what means a new value can be entered + by hand. This is similar to QComboBox::setEditable(). */ + void setRelatedDataEditable(bool set); + + /*! Only usable if related data is set (ie. this is for combo boxes). + \return 'editable' flag for this column. + False by default. @see setRelatedDataEditable(bool). */ + inline bool relatedDataEditable() const { return m_relatedDataEditable; } + + /*! A rich field information for db-aware data. + For not-db-aware data it is always 0 (use field() instead). */ + KexiDB::QueryColumnInfo* columnInfo; + + /*! A rich field information for db-aware data. Specifies information for a column + that should be visible instead of columnInfo. For example case see + @ref KexiDB::QueryColumnInfo::Vector KexiDB::QuerySchema::fieldsExpanded(KexiDB::QuerySchema::FieldsExpandedOptions options = Default) + + For not-db-aware data it is always 0. */ + KexiDB::QueryColumnInfo* visibleLookupColumnInfo; + + bool isDBAware : 1; //!< true if data is stored in DB, not only in memeory + +/* QString caption; + int type; //!< one of KexiDB::Field::Type + uint width; +*/ +// bool isNull() const; + +/* virtual QString caption() const; + virtual void setCaption(const QString& c); + */ + protected: + //! special ctor that do not allocate d member; + KexiTableViewColumn(bool); + + void init(); + + QString m_captionAliasOrName; + + QIconSet m_icon; + + KexiUtils::Validator* m_validator; + + //! Data that this column is assigned to. + KexiTableViewData* m_data; + + KexiTableViewData* m_relatedData; + uint m_relatedDataPKeyID; + + KexiDB::Field* m_field; + + bool m_readOnly : 1; + bool m_fieldOwned : 1; + bool m_visible : 1; + bool m_relatedDataEditable : 1; + bool m_headerTextVisible : 1; + + friend class KexiTableViewData; +}; + + +/*! List of column definitions. */ +//typedef QValueVector<KexiTableViewColumn> KexiTableViewColumnList; + +typedef QPtrList<KexiTableItem> KexiTableViewDataBase; + +/*! Reimplements QPtrList to allow configurable sorting and more. + Original author: Till Busch. + Reimplemented by Jaroslaw Staniek. +*/ +class KEXIDATATABLE_EXPORT KexiTableViewData : public QObject, protected KexiTableViewDataBase +{ + Q_OBJECT + +public: + typedef QPtrListIterator<KexiTableItem> Iterator; + + //! not db-aware version + KexiTableViewData(); + + //! db-aware version + KexiTableViewData(KexiDB::Cursor *c); + +//TODO: make this more generic: allow to add more columns! + /*! Defines two-column table usually used with comboboxes. + First column is invisible and contains key values. + Second column and contains user-visible value. + @param keys a list of keys + @param values a list of text values (must be of the same length as keys list) + @param keyType a type for keys + @param valueType a type for values + */ + KexiTableViewData( + const QValueList<QVariant> &keys, const QValueList<QVariant> &values, + KexiDB::Field::Type keyType = KexiDB::Field::Text, + KexiDB::Field::Type valueType = KexiDB::Field::Text); + + /*! Like above constructor, but keys and values are not provided. + You can do this later by calling append(KexiTableItem*) method. + (KexiTableItem object must have exactly two columns) + */ + KexiTableViewData(KexiDB::Field::Type keyType, KexiDB::Field::Type valueType); + + virtual ~KexiTableViewData(); +//js void setSorting(int key, bool order=true, short type=1); + + /*! Preloads all rows provided by cursor (only for db-aware version). */ +//! @todo change to bool and return false on error! + void preloadAllRows(); + + /*! Sets sorting for \a column. If \a column is -1, sorting is disabled. */ + void setSorting(int column, bool ascending=true); + + /*! \return the column number by which the data is sorted, + or -1 if sorting is disabled. In this case sortingOrder() will return 0. + Initial sorted column number for data after instantiating object is -1. */ + inline int sortedColumn() const { return m_sortedColumn; } + + /*! \return 1 if ascending sort order is set, -1 id descending sort order is set, + or 0 if no sorting is set. This is independent of whether data is sorted now. + Initial sorting for data after instantiating object is 0. */ + inline int sortingOrder() const { return m_order; } + + /*! Adds column \a col. + Warning: \a col will be owned by this object, and deleted on its destruction. */ + void addColumn( KexiTableViewColumn* col ); + + inline int globalColumnID(int visibleID) { return m_globalColumnsIDs.at( visibleID ); } + inline int visibleColumnID(int globalID) { return m_visibleColumnsIDs.at( globalID ); } + + /*virtual?*/ + /*! \return true if this db-aware data set. */ + inline bool isDBAware() { return m_cursor; } + + /*! For db-aware data set only: table name is returned; + equivalent to cursor()->query()->parentTable()->name(). */ + QString dbTableName() const; + + inline KexiDB::Cursor* cursor() const { return m_cursor; } + + inline uint columnsCount() const { return columns.count(); } + + inline KexiTableViewColumn* column(uint c) { return columns.at(c); } + + /*! Columns information */ + KexiTableViewColumn::List columns; + + /*! \return true if data is not editable. Can be set using setReadOnly() + but it's still true if database cursor returned by cursor() + is not 0 and has read-only connection. */ + virtual bool isReadOnly() const; + + /*! Sets readOnly flag for this data. + If \a set is true, insertingEnabled flag will be cleared automatically. + \sa isInsertingEnabled() */ + virtual void setReadOnly(bool set); + + /*! \return true if data inserting is enabled (the default). */ + virtual bool isInsertingEnabled() const { return m_insertingEnabled; } + + /*! Sets insertingEnabled flag. If true, empty row is available + If \a set is true, read-only flag will be cleared automatically. + \sa setReadOnly() */ + virtual void setInsertingEnabled(bool set); + + /*! Clears and initializes internal row edit buffer for incoming editing. + Creates buffer using KexiDB::RowEditBuffer(false) (false means not db-aware type) + if our data is not db-aware, + or db-aware buffer if data is db-aware (isDBAware()==true). + \sa KexiDB::RowEditBuffer + */ + void clearRowEditBuffer(); + + /*! Updates internal row edit buffer: currently edited column \a col (number \a colnum) + has now assigned new value of \a newval. + Uses column's caption to address the column in buffer + if the buffer is of simple type, or db-aware buffer if (isDBAware()==true). + (then fields are addressed with KexiDB::Field, instead of caption strings). + If \a allowSignals is true (the default), aboutToChangeCell() signal is emitted. + \a visibleValueForLookupField allows to pass visible value (usually a text) + for a lookup field (only reasonable if col->visibleLookupColumnInfo != 0). + Note that \a newval may be changed in aboutToChangeCell() signal handler. + \sa KexiDB::RowEditBuffer */ + bool updateRowEditBufferRef(KexiTableItem *item, + int colnum, KexiTableViewColumn* col, QVariant& newval, bool allowSignals = true, + QVariant *visibleValueForLookupField = 0); + + /*! Added for convenience. Like above but \a newval is passed by value. */ + inline bool updateRowEditBuffer(KexiTableItem *item, int colnum, KexiTableViewColumn* col, + QVariant newval, bool allowSignals = true) + { + QVariant newv(newval); + return updateRowEditBufferRef(item, colnum, col, newv, allowSignals); + } + + /*! Added for convenience. Like above but it's assumed that \a item item's columns are ordered + like in table view, not like in form view. Don't use this with form views. */ + inline bool updateRowEditBuffer(KexiTableItem *item, int colnum, + QVariant newval, bool allowSignals = true) + { + KexiTableViewColumn* col = columns.at(colnum); + return col ? updateRowEditBufferRef(item, colnum, col, newval, allowSignals) : false; + } + + inline KexiDB::RowEditBuffer* rowEditBuffer() const { return m_pRowEditBuffer; } + + /*! \return last operation's result information (always not null). */ + inline KexiDB::ResultInfo* result() { return &m_result; } + + bool saveRowChanges(KexiTableItem& item, bool repaint = false); + + bool saveNewRow(KexiTableItem& item, bool repaint = false); + + bool deleteRow(KexiTableItem& item, bool repaint = false); + + /*! Deletes rows (by number) passed with \a rowsToDelete. + Currently, this method is only for non data-aware tables. */ + void deleteRows( const QValueList<int> &rowsToDelete, bool repaint = false ); + + /*! Deletes all rows. Works either for db-aware and non db-aware tables. + Column's definition is not changed. + For db-aware version, all rows are removed from a database. + Row-edit buffer is cleared. + + If \a repaint is true, reloadRequested() signal + is emitted after deleting (if at least one row was deleted), + so presenters can repaint their contents. + + \return true on success. */ + virtual bool deleteAllRows(bool repaint = false); + + /*! @internal method, used mostly by specialized classes like KexiTableView. + Clears internal row structures. Row-edit buffer is cleared. + Does not touch data @ database backend. + Use deleteAllRows() to safely delete all rows. */ + virtual void clearInternal(); + + /*! Inserts new \a item at index \a index. + \a item will be owned by this data object. + Note: Reasonable only for not not-db-aware version. */ + void insertRow(KexiTableItem& item, uint index, bool repaint = false); + +/*TODO: add this as well? + void insertRow(KexiTableItem& item, KexiTableItem& aboveItem); */ + + //! \return index of autoincremented column. The result is cached. +//! \todo what about multiple autoinc columns? +//! \todo what about changing column order? + int autoIncrementedColumn(); + + //! Emits reloadRequested() signal to reload presenters. + void reload() { emit reloadRequested(); } + + inline KexiTableItem* at( uint index ) { return KexiTableViewDataBase::at(index); } + inline virtual uint count() const { return KexiTableViewDataBase::count(); } + inline bool isEmpty () const { return KexiTableViewDataBase::isEmpty(); } + inline KexiTableItem* first() { return KexiTableViewDataBase::first(); } + inline KexiTableItem* last() { return KexiTableViewDataBase::last(); } + inline int findRef( const KexiTableItem* item ) { return KexiTableViewDataBase::findRef(item); } + inline void sort() { KexiTableViewDataBase::sort(); } + inline bool removeFirst() { return KexiTableViewDataBase::removeFirst(); } + inline bool removeLast() { return KexiTableViewDataBase::removeLast(); } + inline void append( const KexiTableItem* item ) { KexiTableViewDataBase::append(item); } + inline void prepend( const KexiTableItem* item ) { KexiTableViewDataBase::prepend(item); } + inline Iterator iterator() { return Iterator(*this); } + inline Iterator* createIterator() { return new Iterator(*this); } + + /*! \return true if ROWID information is stored within every row. + Only reasonable for db-aware version. ROWID information is available + if DriverBehaviour::ROW_ID_FIELD_RETURNS_LAST_AUTOINCREMENTED_VALUE == false + for a KexiDB database driver and a table has no primary key defined. + Phisically, ROWID information is stored after last KexiTableItem's element, + so every KexiTableItem's length is expanded by one. */ + inline bool containsROWIDInfo() const { return m_containsROWIDInfo; } + + inline KexiTableItem* createItem() const + { return new KexiTableItem(m_itemSize); } + +public slots: + //! @internal The same as QObject::deleteLater() but also sets smart pointer m_cursor to 0 to avoid crashes... + void deleteLater(); + +signals: + void destroying(); + + /*! Emitted before change of the single, currently edited cell. + Connect this signal to your slot and set \a result->success to false + to disallow this change. You can also change \a newValue to other value, + or change other columns in \a item row. */ + void aboutToChangeCell(KexiTableItem *item, int colnum, QVariant& newValue, + KexiDB::ResultInfo* result); + + /*! Emited before inserting of a new, current row. + Connect this signal to your slot and set \a result->success to false + to disallow this inserting. You can also change columns in \a item row. */ + void aboutToInsertRow(KexiTableItem *item, KexiDB::ResultInfo* result, bool repaint); + + /*! Emited before changing of an edited, current row. + Connect this signal to your slot and set \a result->success to false + to disallow this change. You can also change columns in \a item row. */ + void aboutToUpdateRow(KexiTableItem *item, KexiDB::RowEditBuffer* buffer, + KexiDB::ResultInfo* result); + + void rowUpdated(KexiTableItem*); //!< Current row has been updated + + void rowInserted(KexiTableItem*, bool repaint); //!< A row has been inserted + + //! A row has been inserted at \a index position (not db-aware data only) + void rowInserted(KexiTableItem*, uint index, bool repaint); + + /*! Emited before deleting of a current row. + Connect this signal to your slot and set \a result->success to false + to disallow this deleting. */ + void aboutToDeleteRow(KexiTableItem& item, KexiDB::ResultInfo* result, bool repaint); + + //! Current row has been deleted + void rowDeleted(); + + //! Rows have been deleted + void rowsDeleted( const QValueList<int> &rowsToDelete ); + + //! Displayed data needs to be reloaded in all presenters. + void reloadRequested(); + + void rowRepaintRequested(KexiTableItem&); + +protected: + void init(); + void init( + const QValueList<QVariant> &keys, const QValueList<QVariant> &values, + KexiDB::Field::Type keyType, KexiDB::Field::Type valueType); + + virtual int compareItems(Item item1, Item item2); + int cmpStr(Item item1, Item item2); + int cmpInt(Item item1, Item item2); + int cmpUInt(Item item1, Item item2); + int cmpLongLong(Item item1, Item item2); + int cmpULongLong(Item item1, Item item2); + int cmpDouble(Item item1, Item item2); + int cmpDate(Item item1, Item item2); + int cmpDateTime(Item item1, Item item2); + int cmpTime(Item item1, Item item2); + + //! Compare function for BLOB data (QByteArray). Uses size as the weight. + int cmpBLOB(Item item1, Item item2); + + //! internal: for saveRowChanges() and saveNewRow() + bool saveRow(KexiTableItem& item, bool insert, bool repaint); + + //! (logical) sorted column number, set by setSorting() + //! can differ from m_realSortedColumn if there's lookup column used + int m_sortedColumn; + //! real sorted column number, set by setSorting(), used by cmp*() methods + int m_realSortedColumn; + short m_order; + short m_type; + int m_itemSize; + static unsigned short charTable[]; + KexiDB::RowEditBuffer *m_pRowEditBuffer; + QGuardedPtr<KexiDB::Cursor> m_cursor; + + //! used to faster lookup columns of simple type (not dbaware) +// QDict<KexiTableViewColumn> *m_simpleColumnsByName; + + KexiDB::ResultInfo m_result; + + uint m_visibleColumnsCount; + QValueVector<int> m_visibleColumnsIDs, m_globalColumnsIDs; + + bool m_readOnly : 1; + bool m_insertingEnabled : 1; + + /*! Used in acceptEditor() to avoid infinite recursion, + eg. when we're calling acceptRowEdit() during cell accepting phase. */ + bool m_inside_acceptEditor : 1; + + //! @see containsROWIDInfo() + bool m_containsROWIDInfo : 1; + + int m_autoIncrementedColumn; + + int (KexiTableViewData::*cmpFunc)(void *, void *); + + //! Temporary, used in compare functions like cmpInt(), cmpString() + //! to avoid memory allocations. + QVariant m_leftTmp, m_rightTmp; +}; + +#endif diff --git a/kexi/widget/tableview/kexitableviewheader.cpp b/kexi/widget/tableview/kexitableviewheader.cpp new file mode 100644 index 00000000..3656a041 --- /dev/null +++ b/kexi/widget/tableview/kexitableviewheader.cpp @@ -0,0 +1,202 @@ +/* This file is part of the KDE project + Copyright (C) 2005-2006 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 "kexitableviewheader.h" + +#include <qapplication.h> +#include <qtooltip.h> +#include <qstyle.h> + +#include <kexiutils/utils.h> +#include <kexiutils/styleproxy.h> + +//! @internal A style that allows to temporary change background color while +//! drawing header section primitive. Used in KexiTableViewHeader. +class KexiTableViewHeaderStyle : public KexiUtils::StyleProxy +{ + public: + KexiTableViewHeaderStyle(QStyle *parentStyle, QWidget *widget) + : KexiUtils::StyleProxy(parentStyle) + { + setBackgroundColor( widget->palette().active().background() ); + } + ~KexiTableViewHeaderStyle() {} + + virtual void drawPrimitive( PrimitiveElement pe, + QPainter *p, const QRect &r, const QColorGroup &cg, SFlags flags = Style_Default, + const QStyleOption& option = QStyleOption::Default ) const + { + if (pe==QStyle::PE_HeaderSection) { + QColorGroup newCg(cg); + newCg.setColor(QColorGroup::Button, m_backgroundColor); + newCg.setColor(QColorGroup::Background, m_backgroundColor); //set background color as well (e.g. for thinkeramik) + m_style->drawPrimitive( pe, p, r, newCg, flags, option ); + return; + } + m_style->drawPrimitive( pe, p, r, cg, flags, option ); + } + + void setBackgroundColor( const QColor& color ) { m_backgroundColor = color; } + + protected: + QColor m_backgroundColor; +}; + +KexiTableViewHeader::KexiTableViewHeader(QWidget * parent, const char * name) + : QHeader(parent, name) + , m_lastToolTipSection(-1) + , m_selectionBackgroundColor(qApp->palette().active().highlight()) + , m_selectedSection(-1) + , m_styleChangeEnabled(true) +{ + styleChange( style() ); + installEventFilter(this); + connect(this, SIGNAL(sizeChange(int,int,int)), + this, SLOT(slotSizeChange(int,int,int))); +} + +KexiTableViewHeader::~KexiTableViewHeader() +{ +} + +void KexiTableViewHeader::styleChange( QStyle& oldStyle ) +{ + QHeader::styleChange( oldStyle ); + if (!m_styleChangeEnabled) + return; + m_styleChangeEnabled = false; + setStyle( new KexiTableViewHeaderStyle(&qApp->style(), this) ); + m_styleChangeEnabled = true; +} + +int KexiTableViewHeader::addLabel ( const QString & s, int size ) +{ + m_toolTips += ""; + slotSizeChange(0,0,0);//refresh + return QHeader::addLabel(s, size); +} + +int KexiTableViewHeader::addLabel ( const QIconSet & iconset, const QString & s, int size ) +{ + m_toolTips += ""; + slotSizeChange(0,0,0);//refresh + return QHeader::addLabel(iconset, s, size); +} + +void KexiTableViewHeader::removeLabel( int section ) +{ + if (section < 0 || section >= count()) + return; + QStringList::Iterator it = m_toolTips.begin(); + it += section; + m_toolTips.remove(it); + slotSizeChange(0,0,0);//refresh + QHeader::removeLabel(section); +} + +void KexiTableViewHeader::setToolTip( int section, const QString & toolTip ) +{ + if (section < 0 || section >= (int)m_toolTips.count()) + return; + m_toolTips[ section ] = toolTip; +} + +bool KexiTableViewHeader::eventFilter(QObject * watched, QEvent * e) +{ + if (e->type()==QEvent::MouseMove) { + const int section = sectionAt( static_cast<QMouseEvent*>(e)->x() ); + if (section != m_lastToolTipSection && section >= 0 && section < (int)m_toolTips.count()) { + QToolTip::remove(this, m_toolTipRect); + QString tip = m_toolTips[ section ]; + if (tip.isEmpty()) { //try label + QFontMetrics fm(font()); + int minWidth = fm.width( label( section ) ) + style().pixelMetric( QStyle::PM_HeaderMargin ); + QIconSet *iset = iconSet( section ); + if (iset) + minWidth += (2+iset->pixmap( QIconSet::Small, QIconSet::Normal ).width()); //taken from QHeader::sectionSizeHint() + if (minWidth > sectionSize( section )) + tip = label( section ); + } + if (tip.isEmpty()) { + m_lastToolTipSection = -1; + } + else { + QToolTip::add(this, m_toolTipRect = sectionRect(section), tip); + m_lastToolTipSection = section; + } + } + } +// if (e->type()==QEvent::MouseButtonPress) { +// todo +// } + return QHeader::eventFilter(watched, e); +} + +void KexiTableViewHeader::slotSizeChange(int /*section*/, int /*oldSize*/, int /*newSize*/ ) +{ + if (m_lastToolTipSection>0) + QToolTip::remove(this, m_toolTipRect); + m_lastToolTipSection = -1; //tooltip's rect is now invalid +} + +void KexiTableViewHeader::setSelectionBackgroundColor(const QColor &color) +{ + m_selectionBackgroundColor = color; +} + +QColor KexiTableViewHeader::selectionBackgroundColor() const +{ + return m_selectionBackgroundColor; +} + +void KexiTableViewHeader::setSelectedSection(int section) +{ + if (m_selectedSection==section || (section!=-1 && section>=count())) + return; + const int oldSection = m_selectedSection; + m_selectedSection = section; + if (oldSection!=-1) + update(sRect(oldSection)); + if (m_selectedSection!=-1) + update(sRect(m_selectedSection)); +} + +int KexiTableViewHeader::selectedSection() const +{ + return m_selectedSection; +} + +void KexiTableViewHeader::paintSection( QPainter * p, int index, const QRect & fr ) +{ + const bool paintSelection = index==m_selectedSection && index != -1; + if (paintSelection) { + static_cast<KexiTableViewHeaderStyle&>(style()).setBackgroundColor( + KexiUtils::blendedColors( + palette().active().background(), m_selectionBackgroundColor, 2, 1) ); + } + + QHeader::paintSection( p, index, fr ); + + if (paintSelection) { //revert the color for subsequent paints + static_cast<KexiTableViewHeaderStyle&>(style()).setBackgroundColor( + palette().active().background()); + } +} + +#include "kexitableviewheader.moc" diff --git a/kexi/widget/tableview/kexitableviewheader.h b/kexi/widget/tableview/kexitableviewheader.h new file mode 100644 index 00000000..5da3fa7b --- /dev/null +++ b/kexi/widget/tableview/kexitableviewheader.h @@ -0,0 +1,75 @@ +/* This file is part of the KDE project + Copyright (C) 2005-2006 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. +*/ + +#ifndef KEXITABLEVIEWHEADER_H +#define KEXITABLEVIEWHEADER_H + +#include <qheader.h> + +class QStyle; + +//! @short A table view header with additional actions. +/*! Displays field description (Field::description()) text as tool tip, if available. + Displays tool tips if a pointed section is not wide enough to fit its label text. + + \todo react on indexChange ( int section, int fromIndex, int toIndex ) signal +*/ +class KEXIDATATABLE_EXPORT KexiTableViewHeader : public QHeader +{ + Q_OBJECT + + public: + KexiTableViewHeader(QWidget * parent = 0, const char * name = 0); + + virtual ~KexiTableViewHeader(); + + int addLabel( const QString & s, int size = -1 ); + + int addLabel( const QIconSet & iconset, const QString & s, int size = -1 ); + + void removeLabel( int section ); + + /*! Sets \a toolTip for \a section. */ + void setToolTip( int section, const QString & toolTip ); + + virtual bool eventFilter(QObject * watched, QEvent * e); + + void setSelectedSection(int section); + int selectedSection() const; + + QColor selectionBackgroundColor() const; + void setSelectionBackgroundColor(const QColor &color); + + protected slots: + void slotSizeChange(int section, int oldSize, int newSize ); + + protected: + virtual void paintSection ( QPainter * p, int index, const QRect & fr ); + virtual void styleChange( QStyle& oldStyle ); + + int m_lastToolTipSection; + QRect m_toolTipRect; + + QStringList m_toolTips; + QColor m_selectionBackgroundColor; + int m_selectedSection; + bool m_styleChangeEnabled : 1; +}; + +#endif diff --git a/kexi/widget/tableview/kexitextformatter.cpp b/kexi/widget/tableview/kexitextformatter.cpp new file mode 100644 index 00000000..f4e0b89d --- /dev/null +++ b/kexi/widget/tableview/kexitextformatter.cpp @@ -0,0 +1,237 @@ +/* This file is part of the KDE project + Copyright (C) 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 <klocale.h> + +#include "kexitextformatter.h" +#include <widget/utils/kexidatetimeformatter.h> +#include <kexidb/utils.h> + +//! @internal +class KexiTextFormatter::Private +{ + public: + Private() : field(0), dateFormatter(0), timeFormatter(0) + { + } + + ~Private() + { + delete dateFormatter; + delete timeFormatter; + } + + KexiDB::Field* field; + KexiDateFormatter *dateFormatter; + KexiTimeFormatter *timeFormatter; +}; + +KexiTextFormatter::KexiTextFormatter() + : d( new Private ) +{ +} + +KexiTextFormatter::~KexiTextFormatter() +{ + delete d; +} + +void KexiTextFormatter::setField( KexiDB::Field* field ) +{ + d->field = field; + if (!d->field) + return; + if (d->field->type() == KexiDB::Field::Date || d->field->type() == KexiDB::Field::DateTime) + d->dateFormatter = new KexiDateFormatter(); + else { + delete d->dateFormatter; + d->dateFormatter = 0; + } + if (d->field->type() == KexiDB::Field::Time || d->field->type() == KexiDB::Field::DateTime) + d->timeFormatter = new KexiTimeFormatter(); + else { + delete d->timeFormatter; + d->timeFormatter = 0; + } +} + +QString KexiTextFormatter::valueToText(const QVariant& value, const QString& add) const +{ + //cases, in order of expected frequency + if (!d->field || d->field->isTextType()) + return value.toString() + add; + else if (d->field->isIntegerType()) { + if (value.toInt() == 0) + return add; //eat 0 + } + else if (d->field->isFPNumericType()) { +//! @todo precision! +//! @todo support 'g' format + if (value.toDouble() == 0.0) + return add.isEmpty() ? "0" : add; //eat 0 +#if 0 //moved to KexiDB::formatNumberForVisibleDecimalPlaces() + QString text( QString::number(value.toDouble(), 'f', + QMAX(d->field->visibleDecimalPlaces(), 10)) ); //!<-- 10 is quite good maximum for fractional digits + //!< @todo add command line settings? +//! @todo (js): get decimal places settings here... + QStringList sl = QStringList::split(".", text); + //nothing + } + else if (sl.count()==2) { +// kdDebug() << "sl.count()=="<<sl.count()<< " " <<sl[0] << " | " << sl[1] << endl; + const QString sl1 = sl[1]; + int pos = sl1.length()-1; + if (pos>=1) { + for (;pos>=0 && sl1[pos]=='0';pos--) + ; + pos++; + } + if (pos>0) + text = sl[0] + m_decsym + sl1.left(pos); + else + text = sl[0]; //no decimal point + } +#endif + return KexiDB::formatNumberForVisibleDecimalPlaces( + value.toDouble(), d->field->visibleDecimalPlaces() ) + add; + } + else if (d->field->type() == KexiDB::Field::Boolean) { +//! @todo temporary solution for booleans! + const bool boolValue = value.isNull() ? QVariant(add).toBool() : value.toBool(); + return boolValue ? "1" : "0"; + } + else if (d->field->type() == KexiDB::Field::Date) { + return d->dateFormatter->dateToString( value.toString().isEmpty() ? QDate() : value.toDate() ); + } + else if (d->field->type() == KexiDB::Field::Time) { + return d->timeFormatter->timeToString( + //hack to avoid converting null variant to valid QTime(0,0,0) + value.toString().isEmpty() ? value.toTime() : QTime(99,0,0) ); + } + else if (d->field->type() == KexiDB::Field::DateTime) { + if (value.toString().isEmpty() ) + return add; + return d->dateFormatter->dateToString( value.toDateTime().date() ) + " " + + d->timeFormatter->timeToString( value.toDateTime().time() ); + } + else if (d->field->type() == KexiDB::Field::BigInteger) { + if (value.toLongLong() == 0) + return add; //eat 0 + } + //default: text + return value.toString() + add; +} + +QVariant KexiTextFormatter::textToValue(const QString& text) const +{ + if (!d->field) + return QVariant(); + const KexiDB::Field::Type t = d->field->type(); + switch (t) { + case KexiDB::Field::Text: + case KexiDB::Field::LongText: + return text; + case KexiDB::Field::Byte: + case KexiDB::Field::ShortInteger: + return text.toShort(); +//! @todo uint, etc? + case KexiDB::Field::Integer: + return text.toInt(); + case KexiDB::Field::BigInteger: + return text.toLongLong(); + case KexiDB::Field::Boolean: +//! @todo temporary solution for booleans! + return text == "1" ? QVariant(true,1) : QVariant(false,0); + case KexiDB::Field::Date: + return d->dateFormatter->stringToVariant( text ); + case KexiDB::Field::Time: + return d->timeFormatter->stringToVariant( text ); + case KexiDB::Field::DateTime: + return stringToDateTime(*d->dateFormatter, *d->timeFormatter, text); + case KexiDB::Field::Float: + case KexiDB::Field::Double: { + // replace custom decimal symbol with '.' as required by to{Float|Double}() + QString fixedText( text ); + fixedText.replace(KGlobal::locale()->decimalSymbol(), "."); + if (t == KexiDB::Field::Double) + return fixedText.toDouble(); + return fixedText.toFloat(); + } + default: + return text; + } +//! @todo more data types! +} + +bool KexiTextFormatter::valueIsEmpty(const QString& text) const +{ + if (text.isEmpty()) + return true; + + if (d->field) { + const KexiDB::Field::Type t = d->field->type(); + if (t == KexiDB::Field::Date) + return d->dateFormatter->isEmpty( text ); + else if (t == KexiDB::Field::Time) + return d->timeFormatter->isEmpty( text ); + else if (t == KexiDB::Field::Time) + return dateTimeIsEmpty( *d->dateFormatter, *d->timeFormatter, text ); + } + +//! @todo + return text.isEmpty(); +} + +bool KexiTextFormatter::valueIsValid(const QString& text) const +{ + if (!d->field) + return true; +//! @todo fix for fields with "required" property = true + if (valueIsEmpty(text)/*ok?*/) + return true; + + const KexiDB::Field::Type t = d->field->type(); + if (t == KexiDB::Field::Date) + return d->dateFormatter->stringToVariant( text ).isValid(); + else if (t == KexiDB::Field::Time) + return d->timeFormatter->stringToVariant( text ).isValid(); + else if (t == KexiDB::Field::DateTime) + return dateTimeIsValid( *d->dateFormatter, *d->timeFormatter, text ); + +//! @todo + return true; +} + +QString KexiTextFormatter::inputMask() const +{ + const KexiDB::Field::Type t = d->field->type(); + if (t==KexiDB::Field::Date) { +//! @todo use KDateWidget? + return d->dateFormatter->inputMask(); + } + else if (t==KexiDB::Field::Time) { +//! @todo use KTimeWidget + d->timeFormatter->inputMask(); + } + else if (t==KexiDB::Field::DateTime) { + dateTimeInputMask( *d->dateFormatter, *d->timeFormatter ); + } + return QString::null; +} + diff --git a/kexi/widget/tableview/kexitextformatter.h b/kexi/widget/tableview/kexitextformatter.h new file mode 100644 index 00000000..3ea611a4 --- /dev/null +++ b/kexi/widget/tableview/kexitextformatter.h @@ -0,0 +1,64 @@ +/* This file is part of the KDE project + Copyright (C) 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. + */ + +#ifndef KEXITEXTFORMATTER_H +#define KEXITEXTFORMATTER_H + +#include <kexidb/field.h> + +//! @short Text formatter used to format QVariant values to text for displaying and back to QVariant +/*! Used by KexiInputTableEdit, KexiDateTableEdit, KexiTimeTableEdit, KexiDateTimeTableEdit, + KexiDBLineEdit (forms), etc. */ +class KEXIDATATABLE_EXPORT KexiTextFormatter +{ + public: + KexiTextFormatter(); + ~KexiTextFormatter(); + + //! Assigns \a field to the formatter. This affects its behaviour. + void setField( KexiDB::Field* field ); + + /*! \return text for \a value. + A field schema set using setField() is used to perform the formatting. + \a add is a text that should be added to the value if possible. + Used in KexiInputTableEdit::setValueInternal(), by form widgets and for reporting/printing. */ + QString valueToText(const QVariant& value, const QString& add) const; + + /*! \return value cnverted from \a text + A field schema set using setField() is used to perform the formatting. + Used in KexiInputTableEdit::setValueInternal(), by form widgets and for reporting/printing. */ + QVariant textToValue(const QString& text) const; + + /*! \return true if value formatted as \a text is empty. + A field schema set using setField() is used to perform the calculation. */ + bool valueIsEmpty(const QString& text) const; + + /*! \return true if value formatted as \a text is valid. + A field schema set using setField() is used to perform the calculation. */ + bool valueIsValid(const QString& text) const; + + /*! \return input mask for intering values related to a field schema + which has been set using setField(). */ + QString inputMask() const; + + class Private; + Private *d; +}; + +#endif diff --git a/kexi/widget/tableview/kexitimetableedit.cpp b/kexi/widget/tableview/kexitimetableedit.cpp new file mode 100644 index 00000000..3238c58e --- /dev/null +++ b/kexi/widget/tableview/kexitimetableedit.cpp @@ -0,0 +1,158 @@ +/* This file is part of the KDE project + Copyright (C) 2004,2006 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 "kexitimetableedit.h" + +#include <qapplication.h> +#include <qpainter.h> +#include <qvariant.h> +#include <qrect.h> +#include <qpalette.h> +#include <qcolor.h> +#include <qfontmetrics.h> +#include <qdatetime.h> +#include <qcursor.h> +#include <qpoint.h> +#include <qlayout.h> +#include <qtoolbutton.h> +#include <qdatetimeedit.h> +#include <qclipboard.h> + +#include <kdebug.h> +#include <klocale.h> +#include <kglobal.h> +#include <kdatepicker.h> +#include <kdatetbl.h> +#include <klineedit.h> +#include <kpopupmenu.h> +#include <kdatewidget.h> + +#include <kexiutils/utils.h> + +KexiTimeTableEdit::KexiTimeTableEdit(KexiTableViewColumn &column, QWidget *parent) + : KexiInputTableEdit(column, parent) +{ + setName("KexiTimeTableEdit"); + +//! @todo add QValidator so time like "99:88:77" cannot be even entered + + m_lineedit->setInputMask( m_formatter.inputMask() ); +} + +KexiTimeTableEdit::~KexiTimeTableEdit() +{ +} + +void KexiTimeTableEdit::setValueInInternalEditor(const QVariant &value) +{ + if (value.isValid() && value.toTime().isValid()) + m_lineedit->setText( m_formatter.timeToString( value.toTime() ) ); + else + m_lineedit->setText( QString::null ); +} + +void KexiTimeTableEdit::setValueInternal(const QVariant& add_, bool removeOld) +{ + if (removeOld) { + //new time entering... just fill the line edit +//! @todo cut string if too long.. + QString add(add_.toString()); + m_lineedit->setText(add); + m_lineedit->setCursorPosition(add.length()); + return; + } + setValueInInternalEditor( m_origValue ); + m_lineedit->setCursorPosition(0); //ok? +} + +void KexiTimeTableEdit::setupContents( QPainter *p, bool focused, const QVariant& val, + QString &txt, int &align, int &x, int &y_offset, int &w, int &h ) +{ + Q_UNUSED(p); + Q_UNUSED(focused); + Q_UNUSED(x); + Q_UNUSED(w); + Q_UNUSED(h); +#ifdef Q_WS_WIN + y_offset = -1; +#else + y_offset = 0; +#endif + if (!val.isNull() && val.canCast(QVariant::Time)) + txt = m_formatter.timeToString(val.toTime()); + align |= AlignLeft; +} + +bool KexiTimeTableEdit::valueIsNull() +{ + if (m_formatter.isEmpty( m_lineedit->text() )) //empty time is null + return true; + return !timeValue().isValid(); +} + +bool KexiTimeTableEdit::valueIsEmpty() +{ + return valueIsNull();// OK? TODO (nonsense?) +} + +QTime KexiTimeTableEdit::timeValue() +{ + return m_formatter.stringToTime( m_lineedit->text() ); +} + +QVariant KexiTimeTableEdit::value() +{ + return m_formatter.stringToVariant( m_lineedit->text() ); +} + +bool KexiTimeTableEdit::valueIsValid() +{ + if (m_formatter.isEmpty( m_lineedit->text() )) //empty time is valid + return true; + return m_formatter.stringToTime( m_lineedit->text() ).isValid(); +} + +void KexiTimeTableEdit::handleCopyAction(const QVariant& value, const QVariant& visibleValue) +{ + Q_UNUSED(visibleValue); + if (!value.isNull() && value.toTime().isValid()) + qApp->clipboard()->setText( m_formatter.timeToString(value.toTime()) ); + else + qApp->clipboard()->setText( QString::null ); +} + +void KexiTimeTableEdit::handleAction(const QString& actionName) +{ + const bool alreadyVisible = m_lineedit->isVisible(); + + if (actionName=="edit_paste") { + const QVariant newValue( m_formatter.stringToTime( qApp->clipboard()->text() ) ); + if (!alreadyVisible) { //paste as the entire text if the cell was not in edit mode + emit editRequested(); + m_lineedit->clear(); + } + setValueInInternalEditor( newValue ); + } + else + KexiInputTableEdit::handleAction(actionName); +} + +KEXI_CELLEDITOR_FACTORY_ITEM_IMPL(KexiTimeEditorFactoryItem, KexiTimeTableEdit) + +#include "kexitimetableedit.moc" diff --git a/kexi/widget/tableview/kexitimetableedit.h b/kexi/widget/tableview/kexitimetableedit.h new file mode 100644 index 00000000..4daa68ec --- /dev/null +++ b/kexi/widget/tableview/kexitimetableedit.h @@ -0,0 +1,64 @@ +/* This file is part of the KDE project + Copyright (C) 2004,2006 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. + */ + +#ifndef KEXITIMETABLEEDIT_H +#define KEXITIMETABLEEDIT_H + +#include "kexiinputtableedit.h" +#include <widget/utils/kexidatetimeformatter.h> + +/*! @short Editor class for Time type. + It is a replacement QTimeEdit due to usability problems: + people are accustomed to use single-character cursor. + Time format is retrieved from the KDE global settings + and input/output is performed using KLineEdit (from KexiInputTableEdit). +*/ +class KexiTimeTableEdit : public KexiInputTableEdit +{ + Q_OBJECT + + public: + KexiTimeTableEdit(KexiTableViewColumn &column, QWidget *parent=0); + virtual ~KexiTimeTableEdit(); + virtual void setupContents( QPainter *p, bool focused, const QVariant& val, + QString &txt, int &align, int &x, int &y_offset, int &w, int &h ); + virtual QVariant value(); + virtual bool valueIsNull(); + virtual bool valueIsEmpty(); + virtual bool valueIsValid(); + + /*! Reimplemented after KexiInputTableEdit. */ + virtual void handleAction(const QString& actionName); + + /*! Reimplemented after KexiInputTableEdit. */ + virtual void handleCopyAction(const QVariant& value, const QVariant& visibleValue); + + protected: + //! helper + void setValueInInternalEditor(const QVariant &value); + virtual void setValueInternal(const QVariant& add, bool removeOld); + QTime timeValue(); + + //! Used to format and convert time values + KexiTimeFormatter m_formatter; +}; + +KEXI_DECLARE_CELLEDITOR_FACTORY_ITEM(KexiTimeEditorFactoryItem) + +#endif |