--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/qt3support/sql/q3sqlmanager_p.cpp Mon Jan 11 14:00:40 2010 +0000
@@ -0,0 +1,961 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the Qt3Support module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "q3sqlmanager_p.h"
+
+#ifndef QT_NO_SQL
+
+#include "qapplication.h"
+#include "qcursor.h"
+#include "qwidget.h"
+#include "q3sqlcursor.h"
+#include "qsqlfield.h"
+#include "q3sqlform.h"
+#include "qsqldriver.h"
+#include "qstring.h"
+#include "qmessagebox.h"
+#include "qbitarray.h"
+
+QT_BEGIN_NAMESPACE
+
+//#define QT_DEBUG_DATAMANAGER
+
+class Q3SqlCursorManagerPrivate
+{
+public:
+ Q3SqlCursorManagerPrivate()
+ : cur(0), autoDelete(false)
+ {}
+
+ QString ftr;
+ QStringList srt;
+ Q3SqlCursor* cur;
+ bool autoDelete;
+};
+
+static QSqlIndex indexFromStringList(const QStringList& l, const Q3SqlCursor* cursor)
+{
+ QSqlIndex newSort;
+ for (int i = 0; i < l.count(); ++i) {
+ QString f = l[i];
+ bool desc = false;
+ if (f.mid(f.length()-3) == QLatin1String("ASC"))
+ f = f.mid(0, f.length()-3);
+ if (f.mid(f.length()-4) == QLatin1String("DESC")) {
+ desc = true;
+ f = f.mid(0, f.length()-4);
+ }
+ int dot = f.lastIndexOf(QLatin1Char('.'));
+ if (dot != -1)
+ f = f.mid(dot+1);
+ const QSqlField field = cursor->field(f.trimmed());
+ if (field.isValid())
+ newSort.append(field, desc);
+ else
+ qWarning("QSqlIndex::indexFromStringList: unknown field: '%s'", f.latin1());
+ }
+ return newSort;
+}
+
+
+/*!
+ \class Q3SqlCursorManager
+ \brief The Q3SqlCursorManager class manages a database cursor.
+
+ \compat
+ \internal
+
+ This class provides common cursor management functionality. This
+ includes saving and applying sorts and filters, refreshing (i.e.,
+ re-selecting) the cursor and searching for records within the
+ cursor.
+
+*/
+
+/*! \internal
+
+ Constructs a cursor manager.
+
+*/
+
+Q3SqlCursorManager::Q3SqlCursorManager()
+{
+ d = new Q3SqlCursorManagerPrivate;
+}
+
+
+/*! \internal
+
+ Destroys the object and frees any allocated resources.
+
+*/
+
+Q3SqlCursorManager::~Q3SqlCursorManager()
+{
+ if (d->autoDelete)
+ delete d->cur;
+ delete d;
+}
+
+/*! \internal
+
+ Sets the manager's sort to the index \a sort. To apply the new
+ sort, use refresh().
+
+ */
+
+void Q3SqlCursorManager::setSort(const QSqlIndex& sort)
+{
+ setSort(sort.toStringList());
+}
+
+/*! \internal
+
+ Sets the manager's sort to the stringlist \a sort. To apply the
+ new sort, use refresh().
+
+ */
+
+void Q3SqlCursorManager::setSort(const QStringList& sort)
+{
+ d->srt = sort;
+}
+
+/*! \internal
+
+ Returns the current sort, or an empty stringlist if there is none.
+
+*/
+
+QStringList Q3SqlCursorManager::sort() const
+{
+ return d->srt;
+}
+
+/*! \internal
+
+ Sets the manager's filter to the string \a filter. To apply the
+ new filter, use refresh().
+
+*/
+
+void Q3SqlCursorManager::setFilter(const QString& filter)
+{
+ d->ftr = filter;
+}
+
+/*! \internal
+
+ Returns the current filter, or an empty string if there is none.
+
+*/
+
+QString Q3SqlCursorManager::filter() const
+{
+ return d->ftr;
+}
+
+/*! \internal
+
+ Sets auto-delete to \a enable. If true, the default cursor will
+ be deleted when necessary.
+
+ \sa autoDelete()
+*/
+
+void Q3SqlCursorManager::setAutoDelete(bool enable)
+{
+ d->autoDelete = enable;
+}
+
+
+/*! \internal
+
+ Returns true if auto-deletion is enabled, otherwise false.
+
+ \sa setAutoDelete()
+
+*/
+
+bool Q3SqlCursorManager::autoDelete() const
+{
+ return d->autoDelete;
+}
+
+/*! \internal
+
+ Sets the default cursor used by the manager to \a cursor. If \a
+ autoDelete is true (the default is false), the manager takes
+ ownership of the \a cursor pointer, which will be deleted when the
+ manager is destroyed, or when setCursor() is called again. To
+ activate the \a cursor use refresh().
+
+ \sa cursor()
+
+*/
+
+void Q3SqlCursorManager::setCursor(Q3SqlCursor* cursor, bool autoDelete)
+{
+ if (d->autoDelete)
+ delete d->cur;
+ d->cur = cursor;
+ d->autoDelete = autoDelete;
+}
+
+/*! \internal
+
+ Returns a pointer to the default cursor used for navigation, or 0
+ if there is no default cursor.
+
+ \sa setCursor()
+
+*/
+
+Q3SqlCursor* Q3SqlCursorManager::cursor() const
+{
+ return d->cur;
+}
+
+
+/*! \internal
+
+ Refreshes the manager using the default cursor. The manager's
+ filter and sort are applied. Returns true on success, false if an
+ error occurred or there is no current cursor.
+
+ \sa setFilter() setSort()
+
+*/
+
+bool Q3SqlCursorManager::refresh()
+{
+ Q3SqlCursor* cur = cursor();
+ if (!cur)
+ return false;
+ QString currentFilter = d->ftr;
+ QStringList currentSort = d->srt;
+ QSqlIndex newSort = indexFromStringList(currentSort, cur);
+ return cur->select(currentFilter, newSort);
+}
+
+/* \internal
+
+ Returns true if the \a buf field values that correspond to \a idx
+ match the field values in \a cur that correspond to \a idx.
+*/
+
+static bool index_matches(const Q3SqlCursor* cur, const QSqlRecord* buf,
+ const QSqlIndex& idx)
+{
+ bool indexEquals = false;
+ for (int i = 0; i < idx.count(); ++i) {
+ const QString fn(idx.field(i).name());
+ if (cur->value(fn) == buf->value(fn))
+ indexEquals = true;
+ else {
+ indexEquals = false;
+ break;
+ }
+ }
+ return indexEquals;
+}
+
+/*
+ Return less than, equal to or greater than 0 if buf1 is less than,
+ equal to or greater than buf2 according to fields described in idx.
+ (### Currently only uses first field.)
+*/
+
+static int compare_recs(const QSqlRecord* buf1, const QSqlRecord* buf2,
+ const QSqlIndex& idx)
+{
+ int cmp = 0;
+
+ int i = 0;
+ const QString fn(idx.field(i).name());
+ const QSqlField f1 = buf1->field(fn);
+
+ if (f1.isValid()) {
+ switch (f1.type()) { // ### more types?
+ case QVariant::String:
+ cmp = f1.value().toString().trimmed().compare(
+ buf2->value(fn).toString().trimmed());
+ break;
+ default:
+ if (f1.value().toDouble() < buf2->value(fn).toDouble())
+ cmp = -1;
+ else if (f1.value().toDouble() > buf2->value(fn).toDouble())
+ cmp = 1;
+ }
+ }
+
+ if (idx.isDescending(i))
+ cmp = -cmp;
+ return cmp;
+}
+
+#ifdef QT_DEBUG_DATAMANAGER
+static void debug_datamanager_buffer(const QString& msg, QSqlRecord* cursor)
+{
+ qDebug("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
+ qDebug("%s", msg.latin1());
+ for (int j = 0; j < cursor->count(); ++j) {
+ qDebug("%s", (cursor->field(j)->name() + " type:"
+ + QString(cursor->field(j)->value().typeName())
+ + " value:" + cursor->field(j)->value().toString())
+ .latin1());
+ }
+}
+#endif
+
+
+/*! \internal
+
+ Relocates the default cursor to the record matching the cursor's
+edit buffer. Only the field names specified by \a idx are used to
+determine an exact match of the cursor to the edit buffer. However,
+other fields in the edit buffer are also used during the search,
+therefore all fields in the edit buffer should be primed with desired
+values for the record being sought. This function is typically used
+to relocate a cursor to the correct position after an insert or
+update. For example:
+
+\snippet doc/src/snippets/code/src_qt3support_sql_q3sqlmanager_p.cpp 0
+
+*/
+
+//## possibly add sizeHint parameter
+bool Q3SqlCursorManager::findBuffer(const QSqlIndex& idx, int atHint)
+{
+#ifdef QT_DEBUG_DATAMANAGER
+ qDebug("Q3SqlCursorManager::findBuffer:");
+#endif
+ Q3SqlCursor* cur = cursor();
+ if (!cur)
+ return false;
+ if (!cur->isActive())
+ return false;
+ if (!idx.count()) {
+ if (cur->at() == QSql::BeforeFirst)
+ cur->next();
+ return false;
+ }
+ QSqlRecord* buf = cur->editBuffer();
+ bool indexEquals = false;
+#ifdef QT_DEBUG_DATAMANAGER
+ qDebug(" Checking hint...");
+#endif
+ /* check the hint */
+ if (cur->seek(atHint))
+ indexEquals = index_matches(cur, buf, idx);
+
+ if (!indexEquals) {
+#ifdef QT_DEBUG_DATAMANAGER
+ qDebug(" Checking current page...");
+#endif
+ /* check current page */
+ int pageSize = 20;
+ int startIdx = qMax(atHint - pageSize, 0);
+ int endIdx = atHint + pageSize;
+ for (int j = startIdx; j <= endIdx; ++j) {
+ if (cur->seek(j)) {
+ indexEquals = index_matches(cur, buf, idx);
+ if (indexEquals)
+ break;
+ }
+ }
+ }
+
+ if (!indexEquals && cur->driver()->hasFeature(QSqlDriver::QuerySize)
+ && cur->sort().count()) {
+#ifdef QT_DEBUG_DATAMANAGER
+ qDebug(" Using binary search...");
+#endif
+ // binary search based on record buffer and current sort fields
+ int lo = 0;
+ int hi = cur->size();
+ int mid;
+ if (compare_recs(buf, cur, cur->sort()) >= 0)
+ lo = cur->at();
+ while (lo != hi) {
+ mid = lo + (hi - lo) / 2;
+ if (!cur->seek(mid))
+ break;
+ if (index_matches(cur, buf, idx)) {
+ indexEquals = true;
+ break;
+ }
+ int c = compare_recs(buf, cur, cur->sort());
+ if (c < 0) {
+ hi = mid;
+ } else if (c == 0) {
+ // found it, but there may be duplicates
+ int at = mid;
+ do {
+ mid--;
+ if (!cur->seek(mid))
+ break;
+ if (index_matches(cur, buf, idx)) {
+ indexEquals = true;
+ break;
+ }
+ } while (compare_recs(buf, cur, cur->sort()) == 0);
+
+ if (!indexEquals) {
+ mid = at;
+ do {
+ mid++;
+ if (!cur->seek(mid))
+ break;
+ if (index_matches(cur, buf, idx)) {
+ indexEquals = true;
+ break;
+ }
+ } while (compare_recs(buf, cur, cur->sort()) == 0);
+ }
+ break;
+ } else if (c > 0) {
+ lo = mid + 1;
+ }
+ }
+ }
+
+ if (!indexEquals) {
+#ifdef QT_DEBUG_DATAMANAGER
+ qDebug(" Using brute search...");
+#endif
+#ifndef QT_NO_CURSOR
+ QApplication::setOverrideCursor(Qt::WaitCursor);
+#endif
+ /* give up, use brute force */
+ int startIdx = 0;
+ if (cur->at() != startIdx) {
+ cur->seek(startIdx);
+ }
+ for (;;) {
+ indexEquals = false;
+ indexEquals = index_matches(cur, buf, idx);
+ if (indexEquals)
+ break;
+ if (!cur->next())
+ break;
+ }
+#ifndef QT_NO_CURSOR
+ QApplication::restoreOverrideCursor();
+#endif
+ }
+#ifdef QT_DEBUG_DATAMANAGER
+ qDebug(" Done, result:" + QString::number(indexEquals));
+#endif
+ return indexEquals;
+}
+
+#ifndef QT_NO_SQL_FORM
+
+class Q3SqlFormManagerPrivate
+{
+public:
+ Q3SqlFormManagerPrivate() : frm(0), rcd(0) {}
+ Q3SqlForm* frm;
+ QSqlRecord* rcd;
+};
+
+
+/*! \internal
+
+ Creates a form manager.
+
+*/
+
+Q3SqlFormManager::Q3SqlFormManager()
+{
+ d = new Q3SqlFormManagerPrivate();
+}
+
+/*! \internal
+
+ Destroys the object and frees any allocated resources.
+
+*/
+
+Q3SqlFormManager::~Q3SqlFormManager()
+{
+ delete d;
+}
+
+/*! \internal
+
+ Clears the default form values. If there is no default form,
+ nothing happens,
+
+*/
+
+void Q3SqlFormManager::clearValues()
+{
+ if (form())
+ form()->clearValues();
+}
+
+/*! \internal
+
+ Sets the form used by the form manager to \a form. If a record has
+ already been assigned to the form manager, that record is also used by
+ the \a form to display data.
+
+ \sa form()
+
+*/
+
+void Q3SqlFormManager::setForm(Q3SqlForm* form)
+{
+ d->frm = form;
+ if (d->rcd && d->frm)
+ d->frm->setRecord(d->rcd);
+}
+
+
+/*! \internal
+
+ Returns the default form used by the form manager, or 0 if there is
+ none.
+
+ \sa setForm()
+
+*/
+
+Q3SqlForm* Q3SqlFormManager::form()
+{
+ return d->frm;
+}
+
+
+/*! \internal
+
+ Sets the record used by the form manager to \a record. If a form has
+ already been assigned to the form manager, \a record is also used by
+ the default form to display data.
+
+ \sa record()
+
+*/
+
+void Q3SqlFormManager::setRecord(QSqlRecord* record)
+{
+ d->rcd = record;
+ if (d->frm) {
+ d->frm->setRecord(d->rcd);
+ }
+}
+
+
+/*! \internal
+
+ Returns the default record used by the form manager, or 0 if there is
+ none.
+
+ \sa setRecord()
+*/
+
+QSqlRecord* Q3SqlFormManager::record()
+{
+ return d->rcd;
+}
+
+
+/*! \internal
+
+ Causes the default form to read its fields . If there is no
+ default form, nothing happens.
+
+ \sa setForm()
+
+*/
+
+void Q3SqlFormManager::readFields()
+{
+ if (d->frm) {
+ d->frm->readFields();
+ }
+}
+
+/*! \internal
+
+ Causes the default form to write its fields . If there is no
+ default form, nothing happens.
+
+ \sa setForm()
+
+*/
+
+void Q3SqlFormManager::writeFields()
+{
+ if (d->frm) {
+ d->frm->writeFields();
+ }
+}
+
+#endif // QT_NO_SQL_FORM
+
+class Q3DataManagerPrivate
+{
+public:
+ Q3DataManagerPrivate()
+ : mode(QSql::None), autoEd(true), confEdits(3),
+ confCancs(false) {}
+ QSql::Op mode;
+ bool autoEd;
+ QBitArray confEdits;
+ bool confCancs;
+
+};
+
+/*!
+ \class Q3DataManager
+
+ \brief The Q3DataManager class is an internal class for implementing
+ the data-aware widgets.
+
+ \internal
+ \compat
+
+ Q3DataManager is a strictly internal class that acts as a base class
+ for other data-aware widgets.
+
+*/
+
+
+/*! \internal
+
+ Constructs an empty data handler.
+
+*/
+
+Q3DataManager::Q3DataManager()
+{
+ d = new Q3DataManagerPrivate();
+}
+
+
+/*! \internal
+
+ Destroys the object and frees any allocated resources.
+
+*/
+
+Q3DataManager::~Q3DataManager()
+{
+ delete d;
+}
+
+
+/*! \internal
+
+ Virtual function which is called when an error has occurred The
+ default implementation displays a warning message to the user with
+ information about the error.
+
+*/
+void Q3DataManager::handleError(QWidget* parent, const QSqlError& e)
+{
+#ifndef QT_NO_MESSAGEBOX
+ if (e.driverText().isEmpty() && e.databaseText().isEmpty()) {
+ QMessageBox::warning (parent, QLatin1String("Warning"), QLatin1String("An error occurred while accessing the database"));
+ } else {
+ QMessageBox::warning (parent, QLatin1String("Warning"), e.driverText() + QLatin1Char('\n') + e.databaseText(),
+ 0, 0);
+ }
+#endif // QT_NO_MESSAGEBOX
+}
+
+
+/*! \internal
+
+ Sets the internal mode to \a m.
+
+*/
+
+void Q3DataManager::setMode(QSql::Op m)
+{
+ d->mode = m;
+}
+
+
+/*! \internal
+
+ Returns the current mode.
+
+*/
+
+QSql::Op Q3DataManager::mode() const
+{
+ return d->mode;
+}
+
+
+/*! \internal
+
+ Sets the auto-edit mode to \a auto.
+
+*/
+
+void Q3DataManager::setAutoEdit(bool autoEdit)
+{
+ d->autoEd = autoEdit;
+}
+
+
+
+/*! \internal
+
+ Returns true if auto-edit mode is enabled; otherwise returns false.
+
+*/
+
+bool Q3DataManager::autoEdit() const
+{
+ return d->autoEd;
+}
+
+/*! \internal
+
+ If \a confirm is true, all edit operations (inserts, updates and
+ deletes) will be confirmed by the user. If \a confirm is false (the
+ default), all edits are posted to the database immediately.
+
+*/
+void Q3DataManager::setConfirmEdits(bool confirm)
+{
+ d->confEdits = QBitArray(d->confEdits.size(), confirm);
+}
+
+/*! \internal
+
+ If \a confirm is true, all inserts will be confirmed by the user.
+ If \a confirm is false (the default), all edits are posted to the
+ database immediately.
+
+*/
+
+void Q3DataManager::setConfirmInsert(bool confirm)
+{
+ d->confEdits[QSql::Insert] = confirm;
+}
+
+/*! \internal
+
+ If \a confirm is true, all updates will be confirmed by the user.
+ If \a confirm is false (the default), all edits are posted to the
+ database immediately.
+
+*/
+
+void Q3DataManager::setConfirmUpdate(bool confirm)
+{
+ d->confEdits[QSql::Update] = confirm;
+}
+
+/*! \internal
+
+ If \a confirm is true, all deletes will be confirmed by the user.
+ If \a confirm is false (the default), all edits are posted to the
+ database immediately.
+
+*/
+
+void Q3DataManager::setConfirmDelete(bool confirm)
+{
+ d->confEdits[QSql::Delete] = confirm;
+}
+
+/*! \internal
+
+ Returns true if the table confirms all edit operations (inserts,
+ updates and deletes), otherwise returns false.
+*/
+
+bool Q3DataManager::confirmEdits() const
+{
+ return (confirmInsert() && confirmUpdate() && confirmDelete());
+}
+
+/*! \internal
+
+ Returns true if the table confirms inserts, otherwise returns
+ false.
+*/
+
+bool Q3DataManager::confirmInsert() const
+{
+ return d->confEdits[QSql::Insert];
+}
+
+/*! \internal
+
+ Returns true if the table confirms updates, otherwise returns
+ false.
+*/
+
+bool Q3DataManager::confirmUpdate() const
+{
+ return d->confEdits[QSql::Update];
+}
+
+/*! \internal
+
+ Returns true if the table confirms deletes, otherwise returns
+ false.
+*/
+
+bool Q3DataManager::confirmDelete() const
+{
+ return d->confEdits[QSql::Delete];
+}
+
+/*! \internal
+
+ If \a confirm is true, all cancels will be confirmed by the user
+ through a message box. If \a confirm is false (the default), all
+ cancels occur immediately.
+*/
+
+void Q3DataManager::setConfirmCancels(bool confirm)
+{
+ d->confCancs = confirm;
+}
+
+/*! \internal
+
+ Returns true if the table confirms cancels, otherwise returns false.
+*/
+
+bool Q3DataManager::confirmCancels() const
+{
+ return d->confCancs;
+}
+
+/*! \internal
+
+ Virtual function which returns a confirmation for an edit of mode \a
+ m. Derived classes can reimplement this function and provide their
+ own confirmation dialog. The default implementation uses a message
+ box which prompts the user to confirm the edit action. The dialog
+ is centered over \a parent.
+
+*/
+
+QSql::Confirm Q3DataManager::confirmEdit(QWidget* parent, QSql::Op m)
+{
+ int ans = 2;
+ if (m == QSql::Delete) {
+#ifndef QT_NO_MESSAGEBOX
+ ans = QMessageBox::information(parent,
+ qApp->translate("QSql", "Delete"),
+ qApp->translate("QSql", "Delete this record?"),
+ qApp->translate("QSql", "Yes"),
+ qApp->translate("QSql", "No"),
+ QString(), 0, 1);
+#else
+ ans = QSql::No;
+#endif // QT_NO_MESSAGEBOX
+ } else if (m != QSql::None) {
+ QString caption;
+ if (m == QSql::Insert) {
+ caption = qApp->translate("QSql", "Insert");
+ } else { // QSql::Update
+ caption = qApp->translate("QSql", "Update");
+ }
+#ifndef QT_NO_MESSAGEBOX
+ ans = QMessageBox::information(parent, caption,
+ qApp->translate("QSql", "Save edits?"),
+ qApp->translate("QSql", "Yes"),
+ qApp->translate("QSql", "No"),
+ qApp->translate("QSql", "Cancel"),
+ 0, 2);
+#else
+ ans = QSql::No;
+#endif // QT_NO_MESSAGEBOX
+ }
+
+ switch (ans) {
+ case 0:
+ return QSql::Yes;
+ case 1:
+ return QSql::No;
+ default:
+ return QSql::Cancel;
+ }
+}
+
+/*! \internal
+
+ Virtual function which returns a confirmation for canceling an edit
+ mode \a m. Derived classes can reimplement this function and
+ provide their own confirmation dialog. The default implementation
+ uses a message box which prompts the user to confirm the edit
+ action. The dialog is centered over \a parent.
+
+
+*/
+
+QSql::Confirm Q3DataManager::confirmCancel(QWidget* parent, QSql::Op)
+{
+#ifndef QT_NO_MESSAGEBOX
+ switch (QMessageBox::information(parent,
+ qApp->translate("QSql", "Confirm"),
+ qApp->translate("QSql", "Cancel your edits?"),
+ qApp->translate("QSql", "Yes"),
+ qApp->translate("QSql", "No"),
+ QString(), 0, 1)) {
+ case 0:
+ return QSql::Yes;
+ case 1:
+ return QSql::No;
+ default:
+ return QSql::Cancel;
+ }
+#else
+ return QSql::Yes;
+#endif // QT_NO_MESSAGEBOX
+}
+
+QT_END_NAMESPACE
+
+#endif