summaryrefslogtreecommitdiffstats
path: root/kexi/widget/tableview
diff options
context:
space:
mode:
authortpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da>2010-01-20 01:29:50 +0000
committertpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da>2010-01-20 01:29:50 +0000
commit8362bf63dea22bbf6736609b0f49c152f975eb63 (patch)
tree0eea3928e39e50fae91d4e68b21b1e6cbae25604 /kexi/widget/tableview
downloadkoffice-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')
-rw-r--r--kexi/widget/tableview/Makefile.am49
-rw-r--r--kexi/widget/tableview/autonumber.pngbin0 -> 244 bytes
-rw-r--r--kexi/widget/tableview/kexiblobtableedit.cpp595
-rw-r--r--kexi/widget/tableview/kexiblobtableedit.h170
-rw-r--r--kexi/widget/tableview/kexibooltableedit.cpp180
-rw-r--r--kexi/widget/tableview/kexibooltableedit.h87
-rw-r--r--kexi/widget/tableview/kexicelleditorfactory.cpp198
-rw-r--r--kexi/widget/tableview/kexicelleditorfactory.h79
-rw-r--r--kexi/widget/tableview/kexicomboboxbase.cpp597
-rw-r--r--kexi/widget/tableview/kexicomboboxbase.h170
-rw-r--r--kexi/widget/tableview/kexicomboboxpopup.cpp373
-rw-r--r--kexi/widget/tableview/kexicomboboxpopup.h92
-rw-r--r--kexi/widget/tableview/kexicomboboxtableedit.cpp446
-rw-r--r--kexi/widget/tableview/kexicomboboxtableedit.h166
-rw-r--r--kexi/widget/tableview/kexidataawareobjectiface.cpp2108
-rw-r--r--kexi/widget/tableview/kexidataawareobjectiface.h918
-rw-r--r--kexi/widget/tableview/kexidataawarepropertyset.cpp260
-rw-r--r--kexi/widget/tableview/kexidataawarepropertyset.h149
-rw-r--r--kexi/widget/tableview/kexidatatableview.cpp121
-rw-r--r--kexi/widget/tableview/kexidatatableview.h94
-rw-r--r--kexi/widget/tableview/kexidatetableedit.cpp290
-rw-r--r--kexi/widget/tableview/kexidatetableedit.h66
-rw-r--r--kexi/widget/tableview/kexidatetimetableedit.cpp165
-rw-r--r--kexi/widget/tableview/kexidatetimetableedit.h69
-rw-r--r--kexi/widget/tableview/kexiinputtableedit.cpp395
-rw-r--r--kexi/widget/tableview/kexiinputtableedit.h126
-rw-r--r--kexi/widget/tableview/kexitableedit.cpp237
-rw-r--r--kexi/widget/tableview/kexitableedit.h233
-rw-r--r--kexi/widget/tableview/kexitableitem.cpp62
-rw-r--r--kexi/widget/tableview/kexitableitem.h58
-rw-r--r--kexi/widget/tableview/kexitableview.cpp2607
-rw-r--r--kexi/widget/tableview/kexitableview.h639
-rw-r--r--kexi/widget/tableview/kexitableview_p.cpp67
-rw-r--r--kexi/widget/tableview/kexitableview_p.h155
-rw-r--r--kexi/widget/tableview/kexitableviewdata.cpp886
-rw-r--r--kexi/widget/tableview/kexitableviewdata.h540
-rw-r--r--kexi/widget/tableview/kexitableviewheader.cpp202
-rw-r--r--kexi/widget/tableview/kexitableviewheader.h75
-rw-r--r--kexi/widget/tableview/kexitextformatter.cpp237
-rw-r--r--kexi/widget/tableview/kexitextformatter.h64
-rw-r--r--kexi/widget/tableview/kexitimetableedit.cpp158
-rw-r--r--kexi/widget/tableview/kexitimetableedit.h64
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
new file mode 100644
index 00000000..23fff353
--- /dev/null
+++ b/kexi/widget/tableview/autonumber.png
Binary files differ
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