From 8362bf63dea22bbf6736609b0f49c152f975eb63 Mon Sep 17 00:00:00 2001 From: tpearson Date: Wed, 20 Jan 2010 01:29:50 +0000 Subject: 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 --- kexi/plugins/tables/Makefile.am | 28 + kexi/plugins/tables/kexilookupcolumnpage.cpp | 419 +++++ kexi/plugins/tables/kexilookupcolumnpage.h | 88 + kexi/plugins/tables/kexitabledesigner_dataview.cpp | 79 + kexi/plugins/tables/kexitabledesigner_dataview.h | 49 + kexi/plugins/tables/kexitabledesignercommands.cpp | 281 +++ kexi/plugins/tables/kexitabledesignercommands.h | 188 ++ kexi/plugins/tables/kexitabledesignerview.cpp | 1943 ++++++++++++++++++++ kexi/plugins/tables/kexitabledesignerview.h | 258 +++ kexi/plugins/tables/kexitabledesignerview_p.cpp | 294 +++ kexi/plugins/tables/kexitabledesignerview_p.h | 191 ++ kexi/plugins/tables/kexitablehandler.desktop | 118 ++ kexi/plugins/tables/kexitablepart.cpp | 313 ++++ kexi/plugins/tables/kexitablepart.h | 100 + kexi/plugins/tables/kexitablepartinstui.rc | 18 + kexi/plugins/tables/kexitablepartui.rc | 7 + 16 files changed, 4374 insertions(+) create mode 100644 kexi/plugins/tables/Makefile.am create mode 100644 kexi/plugins/tables/kexilookupcolumnpage.cpp create mode 100644 kexi/plugins/tables/kexilookupcolumnpage.h create mode 100644 kexi/plugins/tables/kexitabledesigner_dataview.cpp create mode 100644 kexi/plugins/tables/kexitabledesigner_dataview.h create mode 100644 kexi/plugins/tables/kexitabledesignercommands.cpp create mode 100644 kexi/plugins/tables/kexitabledesignercommands.h create mode 100644 kexi/plugins/tables/kexitabledesignerview.cpp create mode 100644 kexi/plugins/tables/kexitabledesignerview.h create mode 100644 kexi/plugins/tables/kexitabledesignerview_p.cpp create mode 100644 kexi/plugins/tables/kexitabledesignerview_p.h create mode 100644 kexi/plugins/tables/kexitablehandler.desktop create mode 100644 kexi/plugins/tables/kexitablepart.cpp create mode 100644 kexi/plugins/tables/kexitablepart.h create mode 100644 kexi/plugins/tables/kexitablepartinstui.rc create mode 100644 kexi/plugins/tables/kexitablepartui.rc (limited to 'kexi/plugins/tables') diff --git a/kexi/plugins/tables/Makefile.am b/kexi/plugins/tables/Makefile.am new file mode 100644 index 00000000..0971a64e --- /dev/null +++ b/kexi/plugins/tables/Makefile.am @@ -0,0 +1,28 @@ +include $(top_srcdir)/kexi/Makefile.global + +kde_module_LTLIBRARIES = kexihandler_table.la + +kexihandler_table_la_SOURCES = kexitablepart.cpp kexitabledesignerview.cpp kexitabledesignerview_p.cpp \ + kexitabledesigner_dataview.cpp kexitabledesignercommands.cpp kexilookupcolumnpage.cpp + +kexihandler_table_la_LDFLAGS = $(all_libraries) $(KDE_PLUGIN) $(VER_INFO) -module +kexihandler_table_la_LIBADD = $(top_builddir)/kexi/core/libkexicore.la \ + $(top_builddir)/kexi/kexidb/libkexidb.la \ + $(top_builddir)/kexi/widget/tableview/libkexidatatable.la \ + $(top_builddir)/kexi/widget/libkexiextendedwidgets.la \ + $(top_builddir)/lib/koproperty/libkoproperty.la + +INCLUDES= $(KOFFICE_INCLUDES) \ + -I$(top_srcdir)/kexi/core -I$(top_srcdir)/kexi \ + -I$(top_srcdir)/kexi/widget -I$(top_srcdir)/kexi/widget/tableview \ + -I$(top_srcdir)/kexi/kexidb -I$(top_srcdir)/lib $(all_includes) + +servicesdir=$(kde_servicesdir)/kexi +services_DATA=kexitablehandler.desktop + +rcdir = $(kde_datadir)/kexi +rc_DATA = kexitablepartui.rc kexitablepartinstui.rc + +METASOURCES = AUTO + +include ../Makefile.common diff --git a/kexi/plugins/tables/kexilookupcolumnpage.cpp b/kexi/plugins/tables/kexilookupcolumnpage.cpp new file mode 100644 index 00000000..9df92794 --- /dev/null +++ b/kexi/plugins/tables/kexilookupcolumnpage.cpp @@ -0,0 +1,419 @@ +/* This file is part of the KDE project + Copyright (C) 2006 Jaroslaw Staniek + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "kexilookupcolumnpage.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +QString mimeTypeToType(const QString& mimeType) +{ + if (mimeType=="kexi/table") + return "table"; + else if (mimeType=="kexi/query") + return "query"; +//! @todo more types + return mimeType; +} + +QString typeToMimeType(const QString& type) +{ + if (type=="table") + return "kexi/table"; + else if (type=="query") + return "kexi/query"; +//! @todo more types + return type; +} + +//---------------------------------------------- + +//! @internal +class KexiLookupColumnPage::Private +{ + public: + Private() + : currentFieldUid(-1) + , insideClearRowSourceSelection(false) + , propertySetEnabled(true) + { + } + ~Private() + { + } + + bool hasPropertySet() const { + return propertySet; + } + + void setPropertySet(KoProperty::Set* aPropertySet) { + propertySet = aPropertySet; + } + + QVariant propertyValue(const QCString& propertyName) const { + return propertySet ? propertySet->property(propertyName).value() : QVariant(); + } + + void changeProperty(const QCString &property, const QVariant &value) + { + if (!propertySetEnabled) + return; + propertySet->changeProperty(property, value); + } + + void updateInfoLabelForPropertySet(const QString& textToDisplayForNullSet) { + KexiPropertyEditorView::updateInfoLabelForPropertySet( + objectInfoLabel, propertySet, textToDisplayForNullSet); + } + + KexiDataSourceComboBox *rowSourceCombo; + KexiFieldComboBox *boundColumnCombo, *visibleColumnCombo; + KexiObjectInfoLabel *objectInfoLabel; + QLabel *rowSourceLabel, *boundColumnLabel, *visibleColumnLabel; + QToolButton *clearRowSourceButton, *gotoRowSourceButton, *clearBoundColumnButton, + *clearVisibleColumnButton; + //! Used only in assignPropertySet() to check whether we already have the set assigned + int currentFieldUid; + + bool insideClearRowSourceSelection : 1; + //! True is changeProperty() works. Used to block updating properties when within assignPropertySet(). + bool propertySetEnabled : 1; + + private: + //! A property set that is displayed on the page. + //! The set is also updated after any change in this page's data. + QGuardedPtr propertySet; +}; + +//---------------------------------------------- + +KexiLookupColumnPage::KexiLookupColumnPage(QWidget *parent) + : QWidget(parent) + , d(new Private()) +{ + setName("KexiLookupColumnPage"); + + QVBoxLayout *vlyr = new QVBoxLayout(this); + d->objectInfoLabel = new KexiObjectInfoLabel(this, "KexiObjectInfoLabel"); + vlyr->addWidget(d->objectInfoLabel); + +//todo d->noDataSourceAvailableSingleText = i18n("No data source could be assigned for this widget."); +//todo d->noDataSourceAvailableMultiText = i18n("No data source could be assigned for multiple widgets."); + + //-Row Source + QWidget *contents = new QWidget(this); + vlyr->addWidget(contents); + QVBoxLayout *contentsVlyr = new QVBoxLayout(contents); + + QHBoxLayout *hlyr = new QHBoxLayout(contentsVlyr); + d->rowSourceLabel = new QLabel(i18n("Row source:"), contents); + d->rowSourceLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); + d->rowSourceLabel->setMargin(2); + d->rowSourceLabel->setMinimumHeight(IconSize(KIcon::Small)+4); + d->rowSourceLabel->setAlignment(Qt::AlignLeft|Qt::AlignBottom); + hlyr->addWidget(d->rowSourceLabel); + + d->gotoRowSourceButton = new KexiSmallToolButton(contents, QString::null, "goto", "gotoRowSourceButton"); + d->gotoRowSourceButton->setMinimumHeight(d->rowSourceLabel->minimumHeight()); + QToolTip::add(d->gotoRowSourceButton, i18n("Go to selected row source")); + hlyr->addWidget(d->gotoRowSourceButton); + connect(d->gotoRowSourceButton, SIGNAL(clicked()), this, SLOT(slotGotoSelectedRowSource())); + + d->clearRowSourceButton = new KexiSmallToolButton(contents, QString::null, + "clear_left", "clearRowSourceButton"); + d->clearRowSourceButton->setMinimumHeight(d->rowSourceLabel->minimumHeight()); + QToolTip::add(d->clearRowSourceButton, i18n("Clear row source")); + hlyr->addWidget(d->clearRowSourceButton); + connect(d->clearRowSourceButton, SIGNAL(clicked()), this, SLOT(clearRowSourceSelection())); + + d->rowSourceCombo = new KexiDataSourceComboBox(contents, "rowSourceCombo"); + d->rowSourceLabel->setBuddy(d->rowSourceCombo); + contentsVlyr->addWidget(d->rowSourceCombo); + + contentsVlyr->addSpacing(8); + + //- Bound Column + hlyr = new QHBoxLayout(contentsVlyr); + d->boundColumnLabel = new QLabel(i18n("Bound column:"), contents); + d->boundColumnLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); + d->boundColumnLabel->setMargin(2); + d->boundColumnLabel->setMinimumHeight(IconSize(KIcon::Small)+4); + d->boundColumnLabel->setAlignment(Qt::AlignLeft|Qt::AlignBottom); + hlyr->addWidget(d->boundColumnLabel); + + d->clearBoundColumnButton = new KexiSmallToolButton(contents, QString::null, + "clear_left", "clearBoundColumnButton"); + d->clearBoundColumnButton->setMinimumHeight(d->boundColumnLabel->minimumHeight()); + QToolTip::add(d->clearBoundColumnButton, i18n("Clear bound column")); + hlyr->addWidget(d->clearBoundColumnButton); + connect(d->clearBoundColumnButton, SIGNAL(clicked()), this, SLOT(clearBoundColumnSelection())); + + d->boundColumnCombo = new KexiFieldComboBox(contents, "boundColumnCombo"); + d->boundColumnLabel->setBuddy(d->boundColumnCombo); + contentsVlyr->addWidget(d->boundColumnCombo); + + contentsVlyr->addSpacing(8); + + //- Visible Column + hlyr = new QHBoxLayout(contentsVlyr); + d->visibleColumnLabel = new QLabel(i18n("Visible column:"), contents); + d->visibleColumnLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); + d->visibleColumnLabel->setMargin(2); + d->visibleColumnLabel->setMinimumHeight(IconSize(KIcon::Small)+4); + d->visibleColumnLabel->setAlignment(Qt::AlignLeft|Qt::AlignBottom); + hlyr->addWidget(d->visibleColumnLabel); + + d->clearVisibleColumnButton = new KexiSmallToolButton(contents, QString::null, + "clear_left", "clearVisibleColumnButton"); + d->clearVisibleColumnButton->setMinimumHeight(d->visibleColumnLabel->minimumHeight()); + QToolTip::add(d->clearVisibleColumnButton, i18n("Clear visible column")); + hlyr->addWidget(d->clearVisibleColumnButton); + connect(d->clearVisibleColumnButton, SIGNAL(clicked()), this, SLOT(clearVisibleColumnSelection())); + + d->visibleColumnCombo = new KexiFieldComboBox(contents, "visibleColumnCombo"); + d->visibleColumnLabel->setBuddy(d->visibleColumnCombo); + contentsVlyr->addWidget(d->visibleColumnCombo); + + vlyr->addStretch(1); + + connect(d->rowSourceCombo, SIGNAL(textChanged(const QString &)), + this, SLOT(slotRowSourceTextChanged(const QString &))); + connect(d->rowSourceCombo, SIGNAL(dataSourceChanged()), this, SLOT(slotRowSourceChanged())); + connect(d->boundColumnCombo, SIGNAL(selected()), this, SLOT(slotBoundColumnSelected())); + connect(d->visibleColumnCombo, SIGNAL(selected()), this, SLOT(slotVisibleColumnSelected())); + + clearBoundColumnSelection(); + clearVisibleColumnSelection(); +} + +KexiLookupColumnPage::~KexiLookupColumnPage() +{ + delete d; +} + +void KexiLookupColumnPage::setProject(KexiProject *prj) +{ + d->rowSourceCombo->setProject(prj, + true/*showTables*/, true/*showQueries*/ + ); + d->boundColumnCombo->setProject(prj); + d->visibleColumnCombo->setProject(prj); +} + +void KexiLookupColumnPage::assignPropertySet(KoProperty::Set* propertySet) +{ + if (!d->hasPropertySet() && !propertySet) + return; + if (propertySet && d->currentFieldUid == (*propertySet)["uid"].value().toInt()) + return; //already assigned + + d->propertySetEnabled = false; + d->setPropertySet( propertySet ); + d->updateInfoLabelForPropertySet( i18n("No field selected") ); + + const bool hasRowSource = d->hasPropertySet() && !d->propertyValue("rowSourceType").isNull() + && !d->propertyValue("rowSource").isNull(); + + QString rowSource, rowSourceType; + if (hasRowSource) { + rowSourceType = typeToMimeType( d->propertyValue("rowSourceType").toString() ); + rowSource = d->propertyValue("rowSource").toString(); + } + d->rowSourceCombo->setDataSource( rowSourceType, rowSource ); + d->rowSourceLabel->setEnabled( d->hasPropertySet() ); + d->rowSourceCombo->setEnabled( d->hasPropertySet() ); + if (!d->hasPropertySet()) + d->clearRowSourceButton->setEnabled( false ); + + int boundColumn = -1, visibleColumn = -1; + if (d->rowSourceCombo->isSelectionValid()) { + boundColumn = d->propertyValue("boundColumn").toInt(); + visibleColumn = d->propertyValue("visibleColumn").toInt(); + } + d->boundColumnCombo->setFieldOrExpression(boundColumn); + d->visibleColumnCombo->setFieldOrExpression(visibleColumn); + updateBoundColumnWidgetsAvailability(); + d->propertySetEnabled = true; +} + +void KexiLookupColumnPage::clearBoundColumnSelection() +{ + d->boundColumnCombo->setCurrentText(""); + d->boundColumnCombo->setFieldOrExpression(QString::null); + slotBoundColumnSelected(); + d->clearBoundColumnButton->setEnabled(false); +} + +void KexiLookupColumnPage::slotBoundColumnSelected() +{ +// KexiDB::Field::Type dataType = KexiDB::Field::InvalidType; +//! @todo this should also work for expressions +/*disabled KexiDB::Field *field = d->fieldListView->schema()->field( d->boundColumnCombo->fieldOrExpression() ); + if (field) + dataType = field->type(); +*/ + d->clearBoundColumnButton->setEnabled( !d->boundColumnCombo->fieldOrExpression().isEmpty() ); + if (!d->boundColumnCombo->fieldOrExpression().isEmpty()) { + kdDebug() << endl; + } + + // update property set + if (d->hasPropertySet()) { + d->changeProperty("boundColumn", d->boundColumnCombo->indexOfField()); + } +/* + emit boundColumnChanged( + d->boundColumnCombo->fieldOrExpression(), + d->boundColumnCombo->fieldOrExpressionCaption(), + dataType + );*/ +} + +void KexiLookupColumnPage::clearVisibleColumnSelection() +{ + d->visibleColumnCombo->setCurrentText(""); + d->visibleColumnCombo->setFieldOrExpression(QString::null); + slotVisibleColumnSelected(); + d->clearVisibleColumnButton->setEnabled(false); +} + +void KexiLookupColumnPage::slotVisibleColumnSelected() +{ +// KexiDB::Field::Type dataType = KexiDB::Field::InvalidType; +//! @todo this should also work for expressions + d->clearVisibleColumnButton->setEnabled( !d->visibleColumnCombo->fieldOrExpression().isEmpty() ); + + // update property set + if (d->hasPropertySet()) { +//! @todo support expression in special "visibleExpression" + d->changeProperty("visibleColumn", d->visibleColumnCombo->indexOfField()); + } +} + +void KexiLookupColumnPage::slotRowSourceChanged() +{ + if (!d->rowSourceCombo->project()) + return; + QString mime = d->rowSourceCombo->selectedMimeType(); + bool rowSourceFound = false; + QString name = d->rowSourceCombo->selectedName(); + if ((mime=="kexi/table" || mime=="kexi/query") && d->rowSourceCombo->isSelectionValid()) { + KexiDB::TableOrQuerySchema *tableOrQuery = new KexiDB::TableOrQuerySchema( + d->rowSourceCombo->project()->dbConnection(), name.latin1(), mime=="kexi/table"); + if (tableOrQuery->table() || tableOrQuery->query()) { +//disabled d->fieldListView->setSchema( tableOrQuery ); +/*tmp*/ delete tableOrQuery; + rowSourceFound = true; + d->boundColumnCombo->setTableOrQuery(name, mime=="kexi/table"); + d->visibleColumnCombo->setTableOrQuery(name, mime=="kexi/table"); + } + else { + delete tableOrQuery; + } + } + if (!rowSourceFound) { + d->boundColumnCombo->setTableOrQuery("", true); + d->visibleColumnCombo->setTableOrQuery("", true); + } + clearBoundColumnSelection(); + clearVisibleColumnSelection(); + d->clearRowSourceButton->setEnabled(rowSourceFound); + d->gotoRowSourceButton->setEnabled(rowSourceFound); +/* disabled + if (dataSourceFound) { + slotFieldListViewSelectionChanged(); + } else { + d->addField->setEnabled(false); + }*/ + updateBoundColumnWidgetsAvailability(); + + //update property set + if (d->hasPropertySet()) { + d->changeProperty("rowSourceType", mimeTypeToType(mime)); + d->changeProperty("rowSource", name); + } + +//disabled emit formDataSourceChanged(mime, name); +//! @todo update d->propertySet ^^ +} + +void KexiLookupColumnPage::slotRowSourceTextChanged(const QString & string) +{ + Q_UNUSED(string); + const bool enable = d->rowSourceCombo->isSelectionValid(); + if (enable) { + updateBoundColumnWidgetsAvailability(); + } + else { + clearRowSourceSelection( d->rowSourceCombo->selectedName().isEmpty()/*alsoClearComboBox*/ ); + } +} + +void KexiLookupColumnPage::clearRowSourceSelection(bool alsoClearComboBox) +{ + if (d->insideClearRowSourceSelection) + return; + d->insideClearRowSourceSelection = true; + if (alsoClearComboBox && !d->rowSourceCombo->selectedName().isEmpty()) + d->rowSourceCombo->setDataSource("", ""); + d->clearRowSourceButton->setEnabled(false); + d->gotoRowSourceButton->setEnabled(false); + d->insideClearRowSourceSelection = false; +} + +void KexiLookupColumnPage::slotGotoSelectedRowSource() +{ + QString mime = d->rowSourceCombo->selectedMimeType(); + if (mime=="kexi/table" || mime=="kexi/query") { + if (d->rowSourceCombo->isSelectionValid()) + emit jumpToObjectRequested(mime.latin1(), d->rowSourceCombo->selectedName().latin1()); + } +} + +void KexiLookupColumnPage::updateBoundColumnWidgetsAvailability() +{ + const bool hasRowSource = d->rowSourceCombo->isSelectionValid(); + d->boundColumnCombo->setEnabled( hasRowSource ); + d->boundColumnLabel->setEnabled( hasRowSource ); + d->clearBoundColumnButton->setEnabled( hasRowSource && !d->boundColumnCombo->fieldOrExpression().isEmpty() ); + d->visibleColumnCombo->setEnabled( hasRowSource ); + d->visibleColumnLabel->setEnabled( hasRowSource ); + d->clearVisibleColumnButton->setEnabled( hasRowSource && !d->visibleColumnCombo->fieldOrExpression().isEmpty() ); +} + +#include "kexilookupcolumnpage.moc" diff --git a/kexi/plugins/tables/kexilookupcolumnpage.h b/kexi/plugins/tables/kexilookupcolumnpage.h new file mode 100644 index 00000000..457b2e3d --- /dev/null +++ b/kexi/plugins/tables/kexilookupcolumnpage.h @@ -0,0 +1,88 @@ +/* This file is part of the KDE project + Copyright (C) 2006 Jaroslaw Staniek + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ +#ifndef KEXILOOKUPCOLUMNPAGE_H +#define KEXILOOKUPCOLUMNPAGE_H + +#include +#include +#include +#include + +class KCommand; +class KexiObjectInfoLabel; +class KexiDataSourceComboBox; +class KexiFieldComboBox; +class KexiFieldListView; +class KexiProject; +class KexiSmallToolButton; +class QToolButton; +class QLabel; +class QFrame; + +//! @short A page within table designer's property pane, providing lookup column editor. +/*! It's data model is basically KexiDB::LookupFieldSchema class, but the page does + not create it directly but instead updates a property set that defines + the field currently selected in the designer. + + @todo not all features of KexiDB::LookupFieldSchema class are displayed on this page yet + */ +class KexiLookupColumnPage : public QWidget +{ + Q_OBJECT + + public: + KexiLookupColumnPage(QWidget *parent); + virtual ~KexiLookupColumnPage(); + + public slots: + void setProject(KexiProject *prj); + void clearRowSourceSelection(bool alsoClearComboBox = true); + void clearBoundColumnSelection(); + void clearVisibleColumnSelection(); + + //! Receives a pointer to a new property \a set (from KexiFormView::managerPropertyChanged()) + void assignPropertySet(KoProperty::Set* propertySet); + + signals: + //! Signal emitted when helper button 'Go to selected row sourcesource' is clicked. + void jumpToObjectRequested(const QCString& mime, const QCString& name); + +// /*! Signal emitted when current bound column has been changed. */ +// void boundColumnChanged(const QString& string, const QString& caption, + // KexiDB::Field::Type type); + + protected slots: + void slotRowSourceTextChanged(const QString & string); + void slotRowSourceChanged(); + void slotGotoSelectedRowSource(); + void slotBoundColumnSelected(); + void slotVisibleColumnSelected(); + + protected: + void updateBoundColumnWidgetsAvailability(); + + //! Used instead of m_propertySet->changeProperty() to honor m_propertySetEnabled + void changeProperty(const QCString &property, const QVariant &value); + + private: + class Private; + Private* d; +}; + +#endif diff --git a/kexi/plugins/tables/kexitabledesigner_dataview.cpp b/kexi/plugins/tables/kexitabledesigner_dataview.cpp new file mode 100644 index 00000000..bea2d9f5 --- /dev/null +++ b/kexi/plugins/tables/kexitabledesigner_dataview.cpp @@ -0,0 +1,79 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Jaroslaw Staniek + + 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 "kexitabledesigner_dataview.h" + +#include +#include +#include +#include "kexitableview.h" +#include "kexidatatableview.h" +#include "keximainwindow.h" + +KexiTableDesigner_DataView::KexiTableDesigner_DataView(KexiMainWindow *win, QWidget *parent) + : KexiDataTable(win, parent, "KexiTableDesigner_DataView", true/*db-aware*/) +{ +} + +KexiTableDesigner_DataView::~KexiTableDesigner_DataView() +{ + if (dynamic_cast(tableView()) + && dynamic_cast(tableView())->cursor()) + { + mainWin()->project()->dbConnection()->deleteCursor( + dynamic_cast(tableView())->cursor() ); + } +} + +tristate KexiTableDesigner_DataView::beforeSwitchTo(int mode, bool &dontStore) +{ + Q_UNUSED( dontStore ); + + if (mode != Kexi::DataViewMode) { + //accept editing before switching +// if (!m_view->acceptRowEdit()) { + if (!acceptRowEdit()) { + return cancelled; + } + } + + return true; +} + +tristate KexiTableDesigner_DataView::afterSwitchFrom(int mode) +{ + Q_UNUSED( mode ); + + if (tempData()->tableSchemaChangedInPreviousView) { + KexiUtils::WaitCursor wait; + KexiDB::Cursor *c = mainWin()->project()->dbConnection()->prepareQuery(*tempData()->table); + if (!c) + return false; + setData(c); + tempData()->tableSchemaChangedInPreviousView = false; + } + return true; +} + +KexiTablePart::TempData* KexiTableDesigner_DataView::tempData() const +{ + return static_cast(parentDialog()->tempData()); +} + +#include "kexitabledesigner_dataview.moc" diff --git a/kexi/plugins/tables/kexitabledesigner_dataview.h b/kexi/plugins/tables/kexitabledesigner_dataview.h new file mode 100644 index 00000000..59e84ab1 --- /dev/null +++ b/kexi/plugins/tables/kexitabledesigner_dataview.h @@ -0,0 +1,49 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Jaroslaw Staniek + + 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 KEXITABLEDESIGNERDATAVIEW_H +#define KEXITABLEDESIGNERDATAVIEW_H + +#include +#include "kexitablepart.h" + +class KexiTableDesigner_DataView : public KexiDataTable +{ + Q_OBJECT + + public: + KexiTableDesigner_DataView(KexiMainWindow *win, QWidget *parent); + + virtual ~KexiTableDesigner_DataView(); + + KexiTablePart::TempData* tempData() const; + + protected: +// //! called just once from ctor +// void init(); +// void initActions(); +// //! called whenever data should be reloaded (on switching to this view mode) +// void initData(); + + virtual tristate beforeSwitchTo(int mode, bool &dontStore); + virtual tristate afterSwitchFrom(int mode); + +}; + +#endif diff --git a/kexi/plugins/tables/kexitabledesignercommands.cpp b/kexi/plugins/tables/kexitabledesignercommands.cpp new file mode 100644 index 00000000..ccbb181a --- /dev/null +++ b/kexi/plugins/tables/kexitabledesignercommands.cpp @@ -0,0 +1,281 @@ +/* This file is part of the KDE project + Copyright (C) 2006 Jaroslaw Staniek + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "kexitabledesignercommands.h" + +using namespace KexiTableDesignerCommands; + + +Command::Command(KexiTableDesignerView* view) + : KCommand() + , m_view(view) +{ +} + +Command::~Command() +{ +} + +//-------------------------------------------------------- + +ChangeFieldPropertyCommand::ChangeFieldPropertyCommand( KexiTableDesignerView* view, + const KoProperty::Set& set, const QCString& propertyName, const QVariant& oldValue, const QVariant& newValue, + KoProperty::Property::ListData* const oldListData, KoProperty::Property::ListData* const newListData) + : Command(view) + , m_alterTableAction( + propertyName=="name" ? oldValue.toString() : set.property("name").value().toString(), + propertyName, newValue, set["uid"].value().toInt()) + , m_oldValue(oldValue) +// , m_fieldUID(set["uid"].value().toInt()) + , m_oldListData( oldListData ? new KoProperty::Property::ListData(*oldListData) : 0 ) + , m_listData( newListData ? new KoProperty::Property::ListData(*newListData) : 0 ) +{ + kexipluginsdbg << "ChangeFieldPropertyCommand: " << debugString() << endl; +} + +ChangeFieldPropertyCommand::~ChangeFieldPropertyCommand() +{ + delete m_oldListData; + delete m_listData; +} + +QString ChangeFieldPropertyCommand::name() const +{ + return i18n("Change \"%1\" property for table field from \"%2\" to \"%3\"") + .arg(m_alterTableAction.propertyName()).arg(m_oldValue.toString()) + .arg(m_alterTableAction.newValue().toString()); +} + +QString ChangeFieldPropertyCommand::debugString() +{ + QString s( name() ); + if (m_oldListData || m_listData) + s += QString("\nAnd list data from [%1]\n to [%2]") + .arg( m_oldListData ? + QString("%1 -> %2") + .arg(m_oldListData->keysAsStringList().join(",")).arg(m_oldListData->names.join(",")) + : QString("")) + .arg( m_listData ? + QString("%1 -> %2") + .arg(m_listData->keysAsStringList().join(",")).arg(m_listData->names.join(",")) + : QString("")); + return s + QString(" (UID=%1)").arg(m_alterTableAction.uid()); +} + +void ChangeFieldPropertyCommand::execute() +{ + m_view->changeFieldProperty( + m_alterTableAction.uid(), + m_alterTableAction.propertyName().latin1(), + m_alterTableAction.newValue(), m_listData ); +} + +void ChangeFieldPropertyCommand::unexecute() +{ + m_view->changeFieldProperty( + m_alterTableAction.uid(), + m_alterTableAction.propertyName().latin1(), + m_oldValue, m_oldListData ); +} + +KexiDB::AlterTableHandler::ActionBase* ChangeFieldPropertyCommand::createAction() +{ + if (m_alterTableAction.propertyName()=="subType") {//skip these properties + return 0; + } + return new KexiDB::AlterTableHandler::ChangeFieldPropertyAction( m_alterTableAction ); +} + +//-------------------------------------------------------- + +RemoveFieldCommand::RemoveFieldCommand( KexiTableDesignerView* view, int fieldIndex, + const KoProperty::Set* set) + : Command(view) + , m_alterTableAction( set ? (*set)["name"].value().toString() : QString::null, + set ? (*set)["uid"].value().toInt() : -1 ) + , m_set( set ? new KoProperty::Set(*set /*deep copy*/) : 0 ) + , m_fieldIndex(fieldIndex) +{ +} + +RemoveFieldCommand::~RemoveFieldCommand() +{ + delete m_set; +} + +QString RemoveFieldCommand::name() const +{ + if (m_set) + return i18n("Remove table field \"%1\"").arg(m_alterTableAction.fieldName()); + + return QString("Remove empty row at position %1").arg(m_fieldIndex); +} + +void RemoveFieldCommand::execute() +{ +// m_view->deleteField( m_fieldIndex ); + m_view->deleteRow( m_fieldIndex ); +} + +void RemoveFieldCommand::unexecute() +{ + m_view->insertEmptyRow(m_fieldIndex); + if (m_set) + m_view->insertField( m_fieldIndex, *m_set ); +} + +QString RemoveFieldCommand::debugString() +{ + if (!m_set) + return name(); + + return name() + "\nAT ROW " + QString::number(m_fieldIndex) + + ", FIELD: " + (*m_set)["caption"].value().toString() + + QString(" (UID=%1)").arg(m_alterTableAction.uid()); +} + +KexiDB::AlterTableHandler::ActionBase* RemoveFieldCommand::createAction() +{ + return new KexiDB::AlterTableHandler::RemoveFieldAction( m_alterTableAction ); +} + +//-------------------------------------------------------- + +InsertFieldCommand::InsertFieldCommand( KexiTableDesignerView* view, + int fieldIndex/*, const KexiDB::Field& field*/, const KoProperty::Set& set ) + : Command(view) + , m_alterTableAction(0) //fieldIndex, new KexiDB::Field(field) /*deep copy*/) + , m_set( set ) //? new KoProperty::Set(*set) : 0 ) +{ + KexiDB::Field *f = view->buildField( m_set ); + if (f) + m_alterTableAction = new KexiDB::AlterTableHandler::InsertFieldAction( + fieldIndex, f, set["uid"].value().toInt()); + else //null action + m_alterTableAction = new KexiDB::AlterTableHandler::InsertFieldAction(true); +} + +InsertFieldCommand::~InsertFieldCommand() +{ + delete m_alterTableAction; +} + +QString InsertFieldCommand::name() const +{ + return i18n("Insert table field \"%1\"").arg(m_set["caption"].value().toString()); +} + +void InsertFieldCommand::execute() +{ + m_view->insertField( m_alterTableAction->index(), /*m_alterTableAction.field(),*/ m_set ); +} + +void InsertFieldCommand::unexecute() +{ + m_view->clearRow( m_alterTableAction->index() );//m_alterTableAction.index() ); +} + +KexiDB::AlterTableHandler::ActionBase* InsertFieldCommand::createAction() +{ + return new KexiDB::AlterTableHandler::InsertFieldAction(*m_alterTableAction); +} + +//-------------------------------------------------------- + +ChangePropertyVisibilityCommand::ChangePropertyVisibilityCommand( KexiTableDesignerView* view, + const KoProperty::Set& set, const QCString& propertyName, bool visible) + : Command(view) + , m_alterTableAction(set.property("name").value().toString(), propertyName, visible, set["uid"].value().toInt()) +// , m_fieldUID(set["uid"].value().toInt()) + , m_oldVisibility( set.property(propertyName).isVisible() ) +{ + kexipluginsdbg << "ChangePropertyVisibilityCommand: " << debugString() << endl; +} + +ChangePropertyVisibilityCommand::~ChangePropertyVisibilityCommand() +{ +} + +QString ChangePropertyVisibilityCommand::name() const +{ + return QString("[internal] Change \"%1\" visibility from \"%2\" to \"%3\"") + .arg(m_alterTableAction.propertyName()) + .arg(m_oldVisibility ? "true" : "false") + .arg(m_alterTableAction.newValue().toBool() ? "true" : "false"); +} + +void ChangePropertyVisibilityCommand::execute() +{ + m_view->changePropertyVisibility( + m_alterTableAction.uid(), + m_alterTableAction.propertyName().latin1(), + m_alterTableAction.newValue().toBool() ); +} + +void ChangePropertyVisibilityCommand::unexecute() +{ + m_view->changePropertyVisibility( + m_alterTableAction.uid(), + m_alterTableAction.propertyName().latin1(), + m_oldVisibility ); +} + +//-------------------------------------------------------- + +InsertEmptyRowCommand::InsertEmptyRowCommand( KexiTableDesignerView* view, int row ) + : Command(view) + , m_alterTableAction(true) //unused, null action + , m_row(row) +{ +} + +InsertEmptyRowCommand::~InsertEmptyRowCommand() +{ +} + +QString InsertEmptyRowCommand::name() const +{ + return QString("Insert empty row at position %1").arg(m_row); +} + +void InsertEmptyRowCommand::execute() +{ + m_view->insertEmptyRow( m_row ); +} + +void InsertEmptyRowCommand::unexecute() +{ + // let's assume the row is empty... + m_view->deleteRow( m_row ); +} + diff --git a/kexi/plugins/tables/kexitabledesignercommands.h b/kexi/plugins/tables/kexitabledesignercommands.h new file mode 100644 index 00000000..355aabe2 --- /dev/null +++ b/kexi/plugins/tables/kexitabledesignercommands.h @@ -0,0 +1,188 @@ +/* This file is part of the KDE project + Copyright (C) 2006 Jaroslaw Staniek + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXITABLEDESIGNER_COMMANDS_H +#define KEXITABLEDESIGNER_COMMANDS_H + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "kexitabledesignerview.h" + +class QWidget; +class QRect; +class QPoint; +class QStringList; +class QCString; + +namespace KexiTableDesignerCommands { + +//! @short Base class for all Table Designer's commands +class Command : public KCommand +{ + public: + Command(KexiTableDesignerView* view); + virtual ~Command(); + + //! Used to collect actions data for AlterTableHandler + //! Can return 0 if the action should not be passed to AlterTableHandler + virtual KexiDB::AlterTableHandler::ActionBase* createAction() { return 0; } + + virtual QString debugString() { return name(); } + + protected: + QGuardedPtr m_view; +}; + +//! @short Undo/redo command used for when changing a property for a table field +class ChangeFieldPropertyCommand : public Command +{ + public: + /*! Creates the ChangeFieldPropertyCommand object. + Note: we use internal "uid" property of a field (set["uid"]) to avoid problems with looking + for field by name when more than one field exists with the same name + (it's invalid but allowed in design time). + \a oldlistData and and \a newListData can be specified so Property::setListData() will be called + on execute() and unexecute(). + */ + ChangeFieldPropertyCommand( KexiTableDesignerView* view, + const KoProperty::Set& set, const QCString& propertyName, + const QVariant& oldValue, const QVariant& newValue, + KoProperty::Property::ListData* const oldListData = 0, KoProperty::Property::ListData* const newListData = 0); + + virtual ~ChangeFieldPropertyCommand(); + + virtual QString name() const; + virtual void execute(); + virtual void unexecute(); + virtual KexiDB::AlterTableHandler::ActionBase* createAction(); + virtual QString debugString(); + + protected: + KexiDB::AlterTableHandler::ChangeFieldPropertyAction m_alterTableAction; + QVariant m_oldValue; +// int m_fieldUID; + KoProperty::Property::ListData* m_oldListData, *m_listData; +}; + +//! @short Undo/redo command used when a field is removed from a table +class RemoveFieldCommand : public Command +{ + public: + /*! Constructs RemoveFieldCommand object. + If \a set is 0, the action only means removing empty row (internal). */ + RemoveFieldCommand( KexiTableDesignerView* view, int fieldIndex, + const KoProperty::Set* set); + + virtual ~RemoveFieldCommand(); + + virtual QString name() const; + virtual void execute(); + virtual void unexecute(); + virtual KexiDB::AlterTableHandler::ActionBase* createAction(); + + virtual QString debugString(); + + protected: + KexiDB::AlterTableHandler::RemoveFieldAction m_alterTableAction; + KoProperty::Set* m_set; + int m_fieldIndex; +}; + +//! @short Undo/redo command used when a new field is inserted into a table +class InsertFieldCommand : public Command +{ + public: + InsertFieldCommand( KexiTableDesignerView* view, + int fieldIndex/*, const KexiDB::Field& field*/, const KoProperty::Set& set ); + virtual ~InsertFieldCommand(); + + virtual QString name() const; + virtual void execute(); + virtual void unexecute(); + virtual KexiDB::AlterTableHandler::ActionBase* createAction(); + + virtual QString debugString() { + return name() + "\nAT ROW " + QString::number(m_alterTableAction->index()) //m_alterTableAction.index()) + + ", FIELD: " + m_set["caption"].value().toString(); //m_alterTableAction.field().debugString(); + } + + protected: + KexiDB::AlterTableHandler::InsertFieldAction *m_alterTableAction; + KoProperty::Set m_set; +}; + + +/* ---- Internal commands follow (not used for building performing ALTER TABLE ---- */ + +//! @short Undo/redo command used when property visibility is changed +/*! Internal, only used in addition to property change. */ +class ChangePropertyVisibilityCommand : public Command +{ + public: + /*! Creates the ChangePropertyVisibilityCommand object. + Note: we use internal "uid" property of a field (set["uid"]) to avoid problems with looking + for field by name when more than one field exists with the same name + (it's invalid but allowed in design time). + */ + ChangePropertyVisibilityCommand( KexiTableDesignerView* view, + const KoProperty::Set& set, const QCString& propertyName, + bool visible); + + virtual ~ChangePropertyVisibilityCommand(); + + virtual QString name() const; + virtual void execute(); + virtual void unexecute(); + + protected: + KexiDB::AlterTableHandler::ChangeFieldPropertyAction m_alterTableAction; +// int m_fieldUID; + bool m_oldVisibility; +}; + +//! @short Undo/redo command used when property visibility is changed +/*! Internal, only used in addition to property change. */ +class InsertEmptyRowCommand : public Command +{ + public: + /*! Creates the InsertEmptyRowCommand object. */ + InsertEmptyRowCommand( KexiTableDesignerView* view, int row ); + virtual ~InsertEmptyRowCommand(); + + virtual QString name() const; + virtual void execute(); + virtual void unexecute(); + + protected: + KexiDB::AlterTableHandler::ChangeFieldPropertyAction m_alterTableAction; + int m_row; +}; + +} + +#endif diff --git a/kexi/plugins/tables/kexitabledesignerview.cpp b/kexi/plugins/tables/kexitabledesignerview.cpp new file mode 100644 index 00000000..7e3478ed --- /dev/null +++ b/kexi/plugins/tables/kexitabledesignerview.cpp @@ -0,0 +1,1943 @@ +/* This file is part of the KDE project + Copyright (C) 2004-2007 Jaroslaw Staniek + + 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 "kexitabledesignerview.h" +#include "kexitabledesignerview_p.h" +#include "kexilookupcolumnpage.h" +#include "kexitabledesignercommands.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//#define MAX_FIELDS 101 //nice prime number + +//! used only for BLOBs +#define DEFAULT_OBJECT_TYPE_VALUE "image" + +//#define KexiTableDesignerView_DEBUG + +//! @todo remove this when BLOBs are implemented +//#define KEXI_NO_BLOB_FIELDS + +using namespace KexiTableDesignerCommands; + +//! @internal Used in tryCastQVariant() anf canCastQVariant() +static bool isIntegerQVariant(QVariant::Type t) +{ + return t==QVariant::LongLong + || t==QVariant::ULongLong + || t==QVariant::Int + || t==QVariant::UInt; +} + +//! @internal Used in tryCastQVariant() +static bool canCastQVariant(QVariant::Type fromType, QVariant::Type toType) +{ + return (fromType==QVariant::Int && toType==QVariant::UInt) + || (fromType==QVariant::CString && toType==QVariant::String) + || (fromType==QVariant::LongLong && toType==QVariant::ULongLong) + || ((fromType==QVariant::String || fromType==QVariant::CString) + && (isIntegerQVariant(toType) || toType==QVariant::Double)); +} + +/*! @internal + \return a variant value converted from \a fromVal to \a toType type. + Null QVariant is returned if \a fromVal's type and \a toType type + are incompatible. */ +static QVariant tryCastQVariant( const QVariant& fromVal, QVariant::Type toType ) +{ + const QVariant::Type fromType = fromVal.type(); + if (fromType == toType) + return fromVal; + if (canCastQVariant(fromType, toType) || canCastQVariant(toType, fromType) + || (isIntegerQVariant(fromType) && toType==QVariant::Double)) + { + QVariant res( fromVal ); + if (res.cast(toType)) + return res; + } + return QVariant(); +} + + +KexiTableDesignerView::KexiTableDesignerView(KexiMainWindow *win, QWidget *parent) + : KexiDataTable(win, parent, "KexiTableDesignerView", false/*not db-aware*/) + , KexiTableDesignerInterface() + , d( new KexiTableDesignerViewPrivate(this) ) +{ + //needed for custom "identifier" property editor widget + KexiCustomPropertyFactory::init(); + + KexiDB::Connection *conn = mainWin()->project()->dbConnection(); + d->view = dynamic_cast(mainWidget()); + + d->data = new KexiTableViewData(); + if (conn->isReadOnly()) + d->data->setReadOnly(true); + d->data->setInsertingEnabled( false ); + + KexiTableViewColumn *col = new KexiTableViewColumn("pk", KexiDB::Field::Text, QString::null, + i18n("Additional information about the field")); + col->setIcon( KexiUtils::colorizeIconToTextColor( SmallIcon("info"), d->view->palette() ) ); + col->setHeaderTextVisible(false); + col->field()->setSubType("KIcon"); + col->setReadOnly(true); + d->data->addColumn( col ); + +// col = new KexiTableViewColumn("name", KexiDB::Field::Text, i18n("Field Name"), + col = new KexiTableViewColumn("caption", KexiDB::Field::Text, i18n("Field Caption"), + i18n("Describes caption for the field")); +// KexiUtils::Validator *vd = new KexiUtils::IdentifierValidator(); +// vd->setAcceptsEmptyValue(true); +// col->setValidator( vd ); + d->data->addColumn( col ); + + col = new KexiTableViewColumn("type", KexiDB::Field::Enum, i18n("Data Type"), + i18n("Describes data type for the field")); + d->data->addColumn( col ); + +#ifdef KEXI_NO_BLOB_FIELDS +//! @todo remove this later + QValueVector types(KexiDB::Field::LastTypeGroup-1); //don't show last type (BLOB) +#else + QValueVector types(KexiDB::Field::LastTypeGroup); +#endif + d->maxTypeNameTextWidth = 0; + QFontMetrics fm(font()); + for (uint i=1; i<=types.count(); i++) { + types[i-1] = KexiDB::Field::typeGroupName(i); + d->maxTypeNameTextWidth = QMAX(d->maxTypeNameTextWidth, fm.width(types[i-1])); + } + col->field()->setEnumHints(types); + + d->data->addColumn( col = new KexiTableViewColumn("comments", KexiDB::Field::Text, i18n("Comments"), + i18n("Describes additional comments for the field")) ); + + d->view->setSpreadSheetMode(); + + connect(d->data, SIGNAL(aboutToChangeCell(KexiTableItem*,int,QVariant&,KexiDB::ResultInfo*)), + this, SLOT(slotBeforeCellChanged(KexiTableItem*,int,QVariant&,KexiDB::ResultInfo*))); + connect(d->data, SIGNAL(rowUpdated(KexiTableItem*)), + this, SLOT(slotRowUpdated(KexiTableItem*))); + //connect(d->data, SIGNAL(aboutToInsertRow(KexiTableItem*,KexiDB::ResultInfo*,bool)), + // this, SLOT(slotAboutToInsertRow(KexiTableItem*,KexiDB::ResultInfo*,bool))); + connect(d->data, SIGNAL(aboutToDeleteRow(KexiTableItem&,KexiDB::ResultInfo*,bool)), + this, SLOT(slotAboutToDeleteRow(KexiTableItem&,KexiDB::ResultInfo*,bool))); + + setMinimumSize(d->view->minimumSizeHint().width(), d->view->minimumSizeHint().height()); + d->view->setFocus(); + + d->sets = new KexiDataAwarePropertySet( this, d->view ); + connect(d->sets, SIGNAL(rowDeleted()), this, SLOT(updateActions())); + connect(d->sets, SIGNAL(rowInserted()), this, SLOT(slotRowInserted())); + + d->contextMenuTitle = new KPopupTitle(d->view->contextMenu()); + d->view->contextMenu()->insertItem(d->contextMenuTitle, -1, 0); + connect(d->view->contextMenu(), SIGNAL(aboutToShow()), this, SLOT(slotAboutToShowContextMenu())); + + plugSharedAction("tablepart_toggle_pkey", this, SLOT(slotTogglePrimaryKey())); + d->action_toggle_pkey = static_cast( sharedAction("tablepart_toggle_pkey") ); + d->action_toggle_pkey->plug(d->view->contextMenu(), 1); //add at the beginning + d->view->contextMenu()->insertSeparator(2); + setAvailable("tablepart_toggle_pkey", !conn->isReadOnly()); + +#ifndef KEXI_NO_UNDOREDO_ALTERTABLE + plugSharedAction("edit_undo", this, SLOT(slotUndo())); + plugSharedAction("edit_redo", this, SLOT(slotRedo())); + setAvailable("edit_undo", false); + setAvailable("edit_redo", false); + connect(d->history, SIGNAL(commandExecuted(KCommand*)), this, SLOT(slotCommandExecuted(KCommand*))); +#endif + +#ifdef KEXI_DEBUG_GUI + KexiUtils::addAlterTableActionDebug(QString::null); //to create the tab + KexiUtils::connectPushButtonActionForDebugWindow( + "simulateAlterTableExecution", this, SLOT(slotSimulateAlterTableExecution())); + KexiUtils::connectPushButtonActionForDebugWindow( + "executeRealAlterTable", this, SLOT(executeRealAlterTable())); +#endif +} + +KexiTableDesignerView::~KexiTableDesignerView() +{ +// removeCurrentPropertySet(); + delete d; +} + +void KexiTableDesignerView::initData() +{ + //add column data +// d->data->clear(); + d->data->deleteAllRows(); + int tableFieldCount = 0; + d->primaryKeyExists = false; + + if (tempData()->table) { + tableFieldCount = tempData()->table->fieldCount(); +//not needed d->sets->clear(tableFieldCount); + + //recreate table data rows + for(int i=0; i < tableFieldCount; i++) { + KexiDB::Field *field = tempData()->table->field(i); + KexiTableItem *item = d->data->createItem(); //new KexiTableItem(0); + if (field->isPrimaryKey()) { + (*item)[COLUMN_ID_ICON] = "key"; + d->primaryKeyExists = true; + } + else { + KexiDB::LookupFieldSchema *lookupFieldSchema + = field->table() ? field->table()->lookupFieldSchema(*field) : 0; + if (lookupFieldSchema && lookupFieldSchema->rowSource().type()!=KexiDB::LookupFieldSchema::RowSource::NoType + && !lookupFieldSchema->rowSource().name().isEmpty()) + { + (*item)[COLUMN_ID_ICON] = "combo"; + } + } + (*item)[COLUMN_ID_CAPTION] = field->captionOrName(); + (*item)[COLUMN_ID_TYPE] = field->typeGroup()-1; //-1 because type groups are counted from 1 + (*item)[COLUMN_ID_DESC] = field->description(); + d->data->append(item); + +//later! createPropertySet( i, field ); + } + } +// else { +// d->sets->clear();//default size +// } + + //add empty space +// const int columnsCount = d->data->columnsCount(); + for (int i=tableFieldCount; i<(int)d->sets->size(); i++) { +// KexiTableItem *item = new KexiTableItem(columnsCount);//3 empty fields + d->data->append(d->data->createItem()); + } + + //set data for our spreadsheet: this will clear our sets + d->view->setData(d->data); + + //now recreate property sets + if (tempData()->table) { + for(int i=0; i < tableFieldCount; i++) { + KexiDB::Field *field = tempData()->table->field(i); + createPropertySet( i, *field ); + } + } + + //column widths + d->view->setColumnWidth(COLUMN_ID_ICON, IconSize( KIcon::Small ) + 10); + d->view->adjustColumnWidthToContents(COLUMN_ID_CAPTION); //adjust column width + d->view->setColumnWidth(COLUMN_ID_TYPE, d->maxTypeNameTextWidth + 2 * d->view->rowHeight()); + d->view->setColumnStretchEnabled( true, COLUMN_ID_DESC ); //last column occupies the rest of the area + const int minCaptionColumnWidth = d->view->fontMetrics().width("wwwwwwwwwww"); + if (minCaptionColumnWidth > d->view->columnWidth(COLUMN_ID_CAPTION)) + d->view->setColumnWidth(COLUMN_ID_CAPTION, minCaptionColumnWidth); + + setDirty(false); + d->view->setCursorPosition(0, COLUMN_ID_CAPTION); //set @ name column + propertySetSwitched(); +} + +//! Gets subtype strings and names for type \a fieldType +void +KexiTableDesignerView::getSubTypeListData(KexiDB::Field::TypeGroup fieldTypeGroup, + QStringList& stringsList, QStringList& namesList) +{ +/* disabled - "mime" is moved from subType to "objectType" custom property + if (fieldTypeGroup==KexiDB::Field::BLOBGroup) { + // special case: BLOB type uses "mime-based" subtypes +//! @todo hardcoded! + stringsList << "image"; + namesList << i18n("Image object type", "Image"); + } + else {*/ + stringsList = KexiDB::typeStringsForGroup(fieldTypeGroup); + namesList = KexiDB::typeNamesForGroup(fieldTypeGroup); +// } + kexipluginsdbg << "KexiTableDesignerView::getSubTypeListData(): subType strings: " << + stringsList.join("|") << "\nnames: " << namesList.join("|") << endl; +} + +KoProperty::Set * +KexiTableDesignerView::createPropertySet( int row, const KexiDB::Field& field, bool newOne ) +{ + QString typeName = "KexiDB::Field::" + field.typeGroupString(); + KoProperty::Set *set = new KoProperty::Set(d->sets, typeName); + if (mainWin()->project()->dbConnection()->isReadOnly()) + set->setReadOnly( true ); +// connect(buff,SIGNAL(propertyChanged(KexiPropertyBuffer&,KexiProperty&)), +// this, SLOT(slotPropertyChanged(KexiPropertyBuffer&,KexiProperty&))); + + KoProperty::Property *prop; + + set->addProperty(prop = new KoProperty::Property("uid", d->generateUniqueId(), "")); + prop->setVisible(false); + + //meta-info for property editor + set->addProperty(prop = new KoProperty::Property("this:classString", i18n("Table field")) ); + prop->setVisible(false); + set->addProperty(prop = new KoProperty::Property("this:iconName", +//! \todo add table_field icon + "lineedit" //"table_field" + )); + prop->setVisible(false); + set->addProperty(prop = new KoProperty::Property("this:useCaptionAsObjectName", + QVariant(true, 1), QString::null)); //we want "caption" to be displayed in the header, not name + prop->setVisible(false); + + //name + set->addProperty(prop + = new KoProperty::Property("name", QVariant(field.name()), i18n("Name"), + QString::null, KexiCustomPropertyFactory::Identifier) ); + + //type + set->addProperty( prop + = new KoProperty::Property("type", QVariant(field.type()), i18n("Type")) ); +#ifndef KexiTableDesignerView_DEBUG + prop->setVisible(false);//always hidden +#endif + + //subtype + QStringList typeStringList, typeNameList; + getSubTypeListData(field.typeGroup(), typeStringList, typeNameList); +/* disabled - "mime" is moved from subType to "objectType" custom property + QString subTypeValue; + if (field.typeGroup()==KexiDB::Field::BLOBGroup) { +// special case: BLOB type uses "mime-based" subtypes +//! @todo this should be retrieved from KexiDB::Field when BLOB supports many different mimetypes + subTypeValue = slist.first(); + } + else {*/ + QString subTypeValue = field.typeString(); + //} + set->addProperty(prop = new KoProperty::Property("subType", + typeStringList, typeNameList, subTypeValue, i18n("Subtype"))); + + // objectType + QStringList objectTypeStringList, objectTypeNameList; +//! @todo this should be retrieved from KexiDB::Field when BLOB supports many different mimetypes + objectTypeStringList << "image"; + objectTypeNameList << i18n("Image object type", "Image"); + QString objectTypeValue( field.customProperty("objectType").toString() ); + if (objectTypeValue.isEmpty()) + objectTypeValue = DEFAULT_OBJECT_TYPE_VALUE; + set->addProperty(prop = new KoProperty::Property("objectType", + objectTypeStringList, objectTypeNameList, objectTypeValue, i18n("Subtype")/*todo other i18n string?*/)); + + set->addProperty( prop + = new KoProperty::Property("caption", QVariant(field.caption()), i18n("Caption") ) ); + prop->setVisible(false);//always hidden + + set->addProperty( prop + = new KoProperty::Property("description", QVariant(field.description())) ); + prop->setVisible(false);//always hidden + + set->addProperty(prop + = new KoProperty::Property("unsigned", QVariant(field.isUnsigned(), 4), i18n("Unsigned Number"))); + + set->addProperty( prop + = new KoProperty::Property("length", (int)field.length()/*200?*/, i18n("Length"))); + + set->addProperty( prop + = new KoProperty::Property("precision", (int)field.precision()/*200?*/, i18n("Precision"))); +#ifdef KEXI_NO_UNFINISHED + prop->setVisible(false); +#endif + set->addProperty( prop + = new KoProperty::Property("visibleDecimalPlaces", field.visibleDecimalPlaces(), i18n("Visible Decimal Places"))); + prop->setOption("min", -1); + prop->setOption("minValueText", i18n("Auto Decimal Places","Auto")); + +//! @todo set reasonable default for column width + set->addProperty( prop + = new KoProperty::Property("width", (int)field.width()/*200?*/, i18n("Column Width"))); +#ifdef KEXI_NO_UNFINISHED + prop->setVisible(false); +#endif + + set->addProperty( prop + = new KoProperty::Property("defaultValue", field.defaultValue(), i18n("Default Value"), + QString::null, +//! @todo use "Variant" type here when supported by KoProperty + (KoProperty::PropertyType)field.variantType()) ); + prop->setOption("3rdState", i18n("None")); +// prop->setVisible(false); + + set->addProperty( prop + = new KoProperty::Property("primaryKey", QVariant(field.isPrimaryKey(), 4), i18n("Primary Key"))); + prop->setIcon("key"); + + set->addProperty( prop + = new KoProperty::Property("unique", QVariant(field.isUniqueKey(), 4), i18n("Unique"))); + + set->addProperty( prop + = new KoProperty::Property("notNull", QVariant(field.isNotNull(), 4), i18n("Required"))); + + set->addProperty( prop + = new KoProperty::Property("allowEmpty", QVariant(!field.isNotEmpty(), 4), i18n("Allow Zero\nSize"))); + + set->addProperty( prop + = new KoProperty::Property("autoIncrement", QVariant(field.isAutoIncrement(), 4), i18n("Autonumber"))); + prop->setIcon("autonumber"); + + set->addProperty( prop + = new KoProperty::Property("indexed", QVariant(field.isIndexed(), 4), i18n("Indexed"))); + + //- properties related to lookup columns (used and set by the "lookup column" tab in the property pane) + KexiDB::LookupFieldSchema *lookupFieldSchema = field.table() ? field.table()->lookupFieldSchema(field) : 0; + set->addProperty( prop = new KoProperty::Property("rowSource", + lookupFieldSchema ? lookupFieldSchema->rowSource().name() : QString::null, i18n("Row Source"))); + prop->setVisible(false); + + set->addProperty( prop = new KoProperty::Property("rowSourceType", + lookupFieldSchema ? lookupFieldSchema->rowSource().typeName() : QString::null, i18n("Row Source\nType"))); + prop->setVisible(false); + + set->addProperty( prop + = new KoProperty::Property("boundColumn", + lookupFieldSchema ? lookupFieldSchema->boundColumn() : -1, i18n("Bound Column"))); + prop->setVisible(false); + +//! @todo this is backward-compatible code for "single visible column" implementation +//! for multiple columns, only the first is displayed, so there is a data loss is GUI is used +//! -- special koproperty editor needed + int visibleColumn = -1; + if (lookupFieldSchema && !lookupFieldSchema->visibleColumns().isEmpty()) + visibleColumn = lookupFieldSchema->visibleColumns().first(); + set->addProperty( prop + = new KoProperty::Property("visibleColumn", visibleColumn, i18n("Visible Column"))); + prop->setVisible(false); + +//! @todo support columnWidths(), columnHeadersVisible(), maximumListRows(), limitToList(), displayWidget() + + //---- + d->updatePropertiesVisibility(field.type(), *set); + + connect(set, SIGNAL(propertyChanged(KoProperty::Set&, KoProperty::Property&)), + this, SLOT(slotPropertyChanged(KoProperty::Set&, KoProperty::Property&))); + + d->sets->insert(row, set, newOne); + return set; +} + +void KexiTableDesignerView::updateActions(bool activated) +{ + Q_UNUSED(activated); +/*! \todo check if we can set pkey for this column type (eg. BLOB?) */ + setAvailable("tablepart_toggle_pkey", propertySet()!=0 && !mainWin()->project()->dbConnection()->isReadOnly()); + if (!propertySet()) + return; + KoProperty::Set &set = *propertySet(); + d->slotTogglePrimaryKeyCalled = true; + d->action_toggle_pkey->setChecked(set["primaryKey"].value().toBool()); + d->slotTogglePrimaryKeyCalled = false; +} + +void KexiTableDesignerView::slotUpdateRowActions(int row) +{ + KexiDataTable::slotUpdateRowActions(row); + updateActions(); +} + +void KexiTableDesignerView::slotTogglePrimaryKey() +{ + if (d->slotTogglePrimaryKeyCalled) + return; + d->slotTogglePrimaryKeyCalled = true; + if (!propertySet()) + return; + KoProperty::Set &set = *propertySet(); + bool isSet = !set["primaryKey"].value().toBool(); + set.changeProperty("primaryKey", QVariant(isSet,1)); //this will update all related properties as well +/* CommandGroup *setPrimaryKeyCommand; + if (isSet) { + setPrimaryKeyCommand = new CommandGroup(i18n("Set primary key for field \"%1\"") + .arg(set["name"].value().toString()) ); + } + else { + setPrimaryKeyCommand = new CommandGroup(i18n("Unset primary key for field \"%1\"") + .arg(set["name"].value().toString()) ); + } + switchPrimaryKey(set, isSet, false, setPrimaryKeyCommand);*/ + //addHistoryCommand( setPrimaryKeyCommand, false /* !execute */ ); + d->slotTogglePrimaryKeyCalled = false; +} + +void KexiTableDesignerView::switchPrimaryKey(KoProperty::Set &propertySet, + bool set, bool aWasPKey, CommandGroup* commandGroup) +{ + const bool was_pkey = aWasPKey || propertySet["primaryKey"].value().toBool(); +// propertySet["primaryKey"] = QVariant(set, 1); + d->setPropertyValueIfNeeded( propertySet, "primaryKey", QVariant(set,1), commandGroup ); + if (&propertySet==this->propertySet()) { + //update action and icon @ column 0 (only if we're changing current property set) + d->action_toggle_pkey->setChecked(set); + if (d->view->selectedItem()) { + //show key in the table + d->view->data()->clearRowEditBuffer(); + d->view->data()->updateRowEditBuffer(d->view->selectedItem(), COLUMN_ID_ICON, + QVariant(set ? "key" : "")); + d->view->data()->saveRowChanges(*d->view->selectedItem(), true); + } + if (was_pkey || set) //change flag only if we're setting pk or really clearing it + d->primaryKeyExists = set; + } + + if (set) { + //primary key is set, remove old pkey if exists + KoProperty::Set *s = 0; + int i; + const int count = (int)d->sets->size(); + for (i=0; isets->at(i); + if (s && s!=&propertySet && (*s)["primaryKey"].value().toBool() && i!=d->view->currentRow()) + break; + } + if (isetPropertyValueIfNeeded( *s, "autoIncrement", QVariant(false,0), commandGroup ); + //(*s)["primaryKey"] = QVariant(false, 0); + d->setPropertyValueIfNeeded( *s, "primaryKey", QVariant(false,0), commandGroup ); + //remove key from table + d->view->data()->clearRowEditBuffer(); + KexiTableItem *item = d->view->itemAt(i); + if (item) { + d->view->data()->updateRowEditBuffer(item, COLUMN_ID_ICON, QVariant()); + d->view->data()->saveRowChanges(*item, true); + } + } + //set unsigned big-integer type +// d->view->data()->saveRowChanges(*d->view->selectedItem()); + d->slotBeforeCellChanged_enabled = false; + d->view->data()->clearRowEditBuffer(); + d->view->data()->updateRowEditBuffer(d->view->selectedItem(), COLUMN_ID_TYPE, + QVariant(KexiDB::Field::IntegerGroup-1/*counting from 0*/)); +// QVariant(KexiDB::Field::typeGroupName(KexiDB::Field::IntegerGroup))); + d->view->data()->saveRowChanges(*d->view->selectedItem(), true); + //propertySet["subType"] = KexiDB::Field::typeString(KexiDB::Field::BigInteger); + d->setPropertyValueIfNeeded( propertySet, "subType", KexiDB::Field::typeString(KexiDB::Field::BigInteger), + commandGroup ); + //propertySet["unsigned"] = QVariant(true,4); + d->setPropertyValueIfNeeded( propertySet, "unsigned", QVariant(true,4), commandGroup ); +/*todo*/ + d->slotBeforeCellChanged_enabled = true; + } + updateActions(); +} + +/*void KexiTableDesignerView::slotCellSelected(int, int row) +{ + kdDebug() << "KexiTableDesignerView::slotCellSelected()" << endl; + if(row == m_row) + return; + m_row = row; + propertyBufferSwitched(); +}*/ + +tristate KexiTableDesignerView::beforeSwitchTo(int mode, bool &dontStore) +{ + if (!d->view->acceptRowEdit()) + return false; +/* if (mode==Kexi::DesignViewMode) { + initData(); + return true; + } + else */ + tristate res = true; + if (mode==Kexi::DataViewMode) { + if (!dirty() && parentDialog()->neverSaved()) { + KMessageBox::sorry(this, i18n("Cannot switch to data view, because table design is empty.\n" + "First, please create your design.") ); + return cancelled; + } +// + else if (dirty() && !parentDialog()->neverSaved()) { +// cancelled = (KMessageBox::No == KMessageBox::questionYesNo(this, i18n("Saving changes for existing table design is not yet supported.\nDo you want to discard your changes now?"))); + +// KexiDB::Connection *conn = mainWin()->project()->dbConnection(); + bool emptyTable; + int r = KMessageBox::warningYesNoCancel(this, + i18n("Saving changes for existing table design is now required.") + + "\n" + d->messageForSavingChanges(emptyTable, /* skip warning? */!isPhysicalAlteringNeeded()), + QString::null, + KStdGuiItem::save(), KStdGuiItem::discard(), QString::null, + KMessageBox::Notify|KMessageBox::Dangerous); + if (r == KMessageBox::Cancel) + res = cancelled; + else + res = true; + dontStore = (r!=KMessageBox::Yes); + if (!dontStore) + d->dontAskOnStoreData = true; +// if (dontStore) +// setDirty(false); + } +// + //todo + return res; + } + else if (mode==Kexi::TextViewMode) { + //todo + } + return res; +} + +tristate KexiTableDesignerView::afterSwitchFrom(int mode) +{ + if (mode==Kexi::NoViewMode || mode==Kexi::DataViewMode) { + initData(); + } + return true; +} + +KoProperty::Set *KexiTableDesignerView::propertySet() +{ + return d->sets ? d->sets->currentPropertySet() : 0; +} + +/* +void KexiTableDesignerView::removeCurrentPropertySet() +{ + const int r = d->view->currentRow(); + KoProperty::Set *buf = d->sets.at(r); + if (!buf) + return; + buf->debug(); +// m_currentBufferCleared = true; + d->sets.remove(r); + propertysetswitched(); +// delete buf; +// m_currentBufferCleared = false; +} +*/ + +void KexiTableDesignerView::slotBeforeCellChanged( + KexiTableItem *item, int colnum, QVariant& newValue, KexiDB::ResultInfo* /*result*/) +{ + if (!d->slotBeforeCellChanged_enabled) + return; +// kdDebug() << d->view->selectedItem() << " " << item + //<< " " << d->sets->at( d->view->currentRow() ) << " " << propertySet() << endl; + if (colnum==COLUMN_ID_CAPTION) {//'caption' +// if (!item->at(1).toString().isEmpty() && item->at(1).isNull()) { + //if 'type' is not filled yet + if (item->at(COLUMN_ID_TYPE).isNull()) { + //auto select 1st row of 'type' column + d->view->data()->updateRowEditBuffer(item, COLUMN_ID_TYPE, QVariant((int)0)); + } + + KoProperty::Set *propertySetForItem = d->sets->findPropertySetForItem(*item); + if (propertySetForItem) { + d->addHistoryCommand_in_slotPropertyChanged_enabled = false; //because we'll add the two changes as one KMacroCommand + QString oldName( propertySetForItem->property("name").value().toString() ); + QString oldCaption( propertySetForItem->property("caption").value().toString() ); + + //we need to create the action now as set["name"] will be changed soon.. + ChangeFieldPropertyCommand *changeCaptionCommand + = new ChangeFieldPropertyCommand( this, *propertySetForItem, "caption", oldCaption, newValue); + + //update field caption and name + propertySetForItem->changeProperty("caption", newValue); + propertySetForItem->changeProperty("name", newValue); // "name" prop. is of custom type Identifier, so this assignment + // will automatically convert newValue to an valid identifier + + //remember this action containing 2 subactions + CommandGroup *changeCaptionAndNameCommand = new CommandGroup( + i18n("Change \"%1\" field's name to \"%2\" and caption from \"%3\" to \"%4\"") + .arg(oldName).arg(propertySetForItem->property("name").value().toString()) + .arg(oldCaption).arg(newValue.toString() )); + changeCaptionAndNameCommand->addCommand( changeCaptionCommand ); +// new ChangeFieldPropertyCommand( this, *propertySetForItem, + // "caption", oldCaption, newValue) + // ); + changeCaptionAndNameCommand->addCommand( + new ChangeFieldPropertyCommand( this, *propertySetForItem, + "name", oldName, propertySetForItem->property("name").value().toString()) + ); + addHistoryCommand( changeCaptionAndNameCommand, false /* !execute */ ); + + d->addHistoryCommand_in_slotPropertyChanged_enabled = true; + } + } + else if (colnum==COLUMN_ID_TYPE) {//'type' + if (newValue.isNull()) { + //'type' col will be cleared: clear all other columns as well + d->slotBeforeCellChanged_enabled = false; + d->view->data()->updateRowEditBuffer(item, COLUMN_ID_ICON, QVariant()); + d->view->data()->updateRowEditBuffer(item, COLUMN_ID_CAPTION, QVariant(QString::null)); + d->view->data()->updateRowEditBuffer(item, COLUMN_ID_DESC, QVariant()); + d->slotBeforeCellChanged_enabled = true; + return; + } + + KoProperty::Set *propertySetForItem = d->sets->findPropertySetForItem(*item); + if (!propertySetForItem) + return; + + KoProperty::Set &set = *propertySetForItem; //propertySet(); + + //'type' col is changed (existed before) + //-get type group number + KexiDB::Field::TypeGroup fieldTypeGroup; + int i_fieldTypeGroup = newValue.toInt()+1/*counting from 1*/; + if (i_fieldTypeGroup < 1 || i_fieldTypeGroup > +#ifdef KEXI_NO_BLOB_FIELDS +//! @todo remove this later + (int)KexiDB::Field::LastTypeGroup-1) //don't show last (BLOB) type +#else + (int)KexiDB::Field::LastTypeGroup) +#endif + return; + fieldTypeGroup = static_cast(i_fieldTypeGroup); + + //-get 1st type from this group, and update 'type' property + KexiDB::Field::Type fieldType = KexiDB::defaultTypeForGroup( fieldTypeGroup ); + if (fieldType==KexiDB::Field::InvalidType) + fieldType = KexiDB::Field::Text; +//moved down set["type"] = (int)fieldType; +// set["subType"] = KexiDB::Field::typeName(fieldType); + + //-get subtypes for this type: keys (slist) and names (nlist) + QStringList slist, nlist; + getSubTypeListData(fieldTypeGroup, slist, nlist); + + QString subTypeValue; +/* disabled - "mime" is moved from subType to "objectType" custom property + if (fieldType==KexiDB::Field::BLOB) { + // special case: BLOB type uses "mime-based" subtypes + subTypeValue = slist.first(); + } + else {*/ + subTypeValue = KexiDB::Field::typeString(fieldType); + //} + KoProperty::Property *subTypeProperty = &set["subType"]; + kexipluginsdbg << subTypeProperty->value() << endl; + + // *** this action contains subactions *** + CommandGroup *changeDataTypeCommand = new CommandGroup( + i18n("Change data type for field \"%1\" to \"%2\"") + .arg(set["name"].value().toString()).arg( KexiDB::Field::typeName( fieldType ) ) ); + +//kexipluginsdbg << "++++++++++" << slist << nlist << endl; + + //update subtype list and value + const bool forcePropertySetReload + = KexiDB::Field::typeGroup( KexiDB::Field::typeForString(subTypeProperty->value().toString()) ) + != fieldTypeGroup; //<-- ????? +// const bool forcePropertySetReload = set["type"].value().toInt() != (int)fieldTypeGroup; + const bool useListData = slist.count() > 1; //disabled-> || fieldType==KexiDB::Field::BLOB; + + if (!useListData) { + slist.clear(); //empty list will be passed + nlist.clear(); + } + d->setPropertyValueIfNeeded( set, "type", (int)fieldType, changeDataTypeCommand, + false /*!forceAddCommand*/, true /*rememberOldValue*/); + + // notNull and defaultValue=false is reasonable for boolean type + if (fieldType == KexiDB::Field::Boolean) { +//! @todo maybe this is good for other data types as well? + d->setPropertyValueIfNeeded( set, "notNull", QVariant(true, 1), changeDataTypeCommand, + false /*!forceAddCommand*/, false /*!rememberOldValue*/); + d->setPropertyValueIfNeeded( set, "defaultValue", QVariant(false, 1), changeDataTypeCommand, + false /*!forceAddCommand*/, false /*!rememberOldValue*/); + } + +/* if (useListData) { + { + subTypeProperty->setListData( slist, nlist ); + } + else { + subTypeProperty->setListData( 0 ); + }*/ + if (set["primaryKey"].value().toBool()==true) { + //primary keys require big int, so if selected type is not integer- remove PK + if (fieldTypeGroup != KexiDB::Field::IntegerGroup) { + /*not needed, line below will do the work + d->view->data()->updateRowEditBuffer(item, COLUMN_ID_ICON, QVariant()); + d->view->data()->saveRowChanges(*item); */ + //set["primaryKey"] = QVariant(false, 1); + d->setPropertyValueIfNeeded( set, "primaryKey", QVariant(false, 1), changeDataTypeCommand ); +//! @todo should we display (passive?) dialog informing about cleared pkey? + } + } +// if (useListData) +// subTypeProperty->setValue( subTypeValue, false/*!rememberOldValue*/ ); + d->setPropertyValueIfNeeded( set, "subType", subTypeValue, + changeDataTypeCommand, false, false /*!rememberOldValue*/, + &slist, &nlist ); + + if (d->updatePropertiesVisibility(fieldType, set, changeDataTypeCommand) || forcePropertySetReload) { + //properties' visiblility changed: refresh prop. set + propertySetReloaded(true); + } + + addHistoryCommand( changeDataTypeCommand, false /* !execute */ ); + } + else if (colnum==COLUMN_ID_DESC) {//'description' + KoProperty::Set *propertySetForItem = d->sets->findPropertySetForItem(*item); + if (!propertySetForItem) + return; + //update field desc. + QVariant oldValue((*propertySetForItem)["description"].value()); + kexipluginsdbg << oldValue << endl; + propertySetForItem->changeProperty("description", newValue); + /*moved addHistoryCommand( + new ChangeFieldPropertyCommand( this, *propertySetForItem, + "description", oldValue, newValue ), false);*/ + } +} + +void KexiTableDesignerView::slotRowUpdated(KexiTableItem *item) +{ + const int row = d->view->data()->findRef(item); + if (row < 0) + return; + + setDirty(); + + //-check if the row was empty before updating + //if yes: we want to add a property set for this new row (field) + QString fieldCaption( item->at(COLUMN_ID_CAPTION).toString() ); + const bool prop_set_allowed = !item->at(COLUMN_ID_TYPE).isNull(); + + if (!prop_set_allowed && d->sets->at(row)/*propertySet()*/) { + //there is a property set, but it's not allowed - remove it: + d->sets->remove( row ); //d->sets->removeCurrentPropertySet(); + + //clear 'type' column: + d->view->data()->clearRowEditBuffer(); +// d->view->data()->updateRowEditBuffer(d->view->selectedItem(), COLUMN_ID_TYPE, QVariant()); + d->view->data()->updateRowEditBuffer(item, COLUMN_ID_TYPE, QVariant()); + d->view->data()->saveRowChanges(*item); + + } else if (prop_set_allowed && !d->sets->at(row)/*propertySet()*/) { + //-- create a new field: + KexiDB::Field::TypeGroup fieldTypeGroup = static_cast( + item->at(COLUMN_ID_TYPE).toInt()+1/*counting from 1*/ ); + int intFieldType = KexiDB::defaultTypeForGroup( fieldTypeGroup ); + if (intFieldType==0) + return; + + QString description( item->at(COLUMN_ID_DESC).toString() ); + +//todo: check uniqueness: + QString fieldName( KexiUtils::string2Identifier(fieldCaption) ); + + KexiDB::Field::Type fieldType = KexiDB::intToFieldType( intFieldType ); + KexiDB::Field field( //tmp + fieldName, + fieldType, + KexiDB::Field::NoConstraints, + KexiDB::Field::NoOptions, + /*length*/0, + /*precision*/0, + /*defaultValue*/QVariant(), + fieldCaption, + description, + /*width*/0); +// m_newTable->addField( field ); + + // reasonable case for boolean type: set notNull flag and "false" as default value + if (fieldType == KexiDB::Field::Boolean) { + field.setNotNull( true ); + field.setDefaultValue( QVariant(false, 0) ); + } + + kexipluginsdbg << "KexiTableDesignerView::slotRowUpdated(): " << field.debugString() << endl; + + //create a new property set: + KoProperty::Set *newSet = createPropertySet( row, field, true ); +//moved + //add a special property indicating that this is brand new buffer, + //not just changed +// KoProperty::Property* prop = new KoProperty::Property("newrow", QVariant()); +// prop->setVisible(false); +// newbuff->addProperty( prop ); + + //refresh property editor: + propertySetSwitched(); + + if (row>=0) { + if (d->addHistoryCommand_in_slotRowUpdated_enabled) { + addHistoryCommand( new InsertFieldCommand( this, row, *newSet /*propertySet()*/ ), //, field /*will be copied*/ + false /* !execute */ ); + } + } + else { + kexipluginswarn << "KexiTableDesignerView::slotRowUpdated() row # not found !" << endl; + } + } +} + +void KexiTableDesignerView::updateActions() +{ + updateActions(false); +} + +void KexiTableDesignerView::slotPropertyChanged(KoProperty::Set& set, KoProperty::Property& property) +{ +// if (!d->slotPropertyChanged_enabled) +// return; + const QCString pname = property.name(); + kexipluginsdbg << "KexiTableDesignerView::slotPropertyChanged(): " << pname << " = " << property.value() + << " (oldvalue = " << property.oldValue() << ")" << endl; + + // true is PK should be altered + bool changePrimaryKey = false; + // true is PK should be set to true, otherwise unset + bool setPrimaryKey = false; + + if (pname=="primaryKey" && d->slotPropertyChanged_primaryKey_enabled) { + changePrimaryKey = true; + setPrimaryKey = property.value().toBool(); + } + + // update "lookup column" icon + if (pname=="rowSource" || pname=="rowSourceType") { +//! @todo indicate invalid definitions of lookup columns as well using a special icon +//! (e.g. due to missing data source) + const int row = d->sets->findRowForPropertyValue("uid", set["uid"].value().toInt()); + KexiTableItem *item = d->view->itemAt(row); + if (item) + d->updateIconForItem(*item, set); + } + + //setting autonumber requires setting PK as well + CommandGroup *setAutonumberCommand = 0; + CommandGroup *toplevelCommand = 0; + if (pname=="autoIncrement" && property.value().toBool()==true) { + if (set["primaryKey"].value().toBool()==false) {//we need PKEY here! + QString msg = QString("

") + +i18n("Setting autonumber requires primary key to be set for current field.")+"

"; + if (d->primaryKeyExists) + msg += (QString("

")+ i18n("Previous primary key will be removed.")+"

"); + msg += (QString("

") + +i18n("Do you want to create primary key for current field? " + "Click \"Cancel\" to cancel setting autonumber.")+"

"); + + if (KMessageBox::Yes == KMessageBox::questionYesNo(this, msg, + i18n("Setting Autonumber Field"), + KGuiItem(i18n("Create &Primary Key"), "key"), KStdGuiItem::cancel() )) + { + changePrimaryKey = true; + setPrimaryKey = true; + //switchPrimaryKey(set, true); + // this will be toplevel command + setAutonumberCommand = new CommandGroup( + i18n("Assign autonumber for field \"%1\"").arg(set["name"].value().toString()) ); + toplevelCommand = setAutonumberCommand; + d->setPropertyValueIfNeeded( set, "autoIncrement", QVariant(true,1), setAutonumberCommand ); + } + else { + setAutonumberCommand = new CommandGroup( + i18n("Remove autonumber from field \"%1\"").arg(set["name"].value().toString()) ); + //d->slotPropertyChanged_enabled = false; +// set["autoIncrement"].setValue( QVariant(false,1), false/*don't save old*/); +// d->slotPropertyChanged_enabled = true; + d->setPropertyValueIfNeeded( set, "autoIncrement", QVariant(false,1), setAutonumberCommand, + true /*forceAddCommand*/, false/*rememberOldValue*/ ); + addHistoryCommand( setAutonumberCommand, false /* !execute */ ); + return; + } + } + } + + //clear PK when these properties were set to false: + if ((pname=="indexed" || pname=="unique" || pname=="notNull") + && set["primaryKey"].value().toBool() && property.value().toBool()==false) + { +//! @todo perhaps show a hint in help panel telling what happens? + changePrimaryKey = true; + setPrimaryKey = false; + // this will be toplevel command + CommandGroup *unsetIndexedOrUniquOrNotNullCommand = new CommandGroup( + i18n("Set \"%1\" property for field \"%2\"").arg(property.caption()).arg(set["name"].value().toString()) ); + toplevelCommand = unsetIndexedOrUniquOrNotNullCommand; + d->setPropertyValueIfNeeded( set, pname, QVariant(false,1), unsetIndexedOrUniquOrNotNullCommand ); + if (pname=="notNull") { +//? d->setPropertyValueIfNeeded( set, "notNull", QVariant(true,1), unsetIndexedOrUniquOrNotNullCommand ); + d->setPropertyValueIfNeeded( set, "unique", QVariant(false,1), unsetIndexedOrUniquOrNotNullCommand ); + } + } + + if (pname=="defaultValue") { + KexiDB::Field::Type type = KexiDB::intToFieldType( set["type"].value().toInt() ); + set["defaultValue"].setType((KoProperty::PropertyType)KexiDB::Field::variantType(type)); + } + + if (pname=="subType" && d->slotPropertyChanged_subType_enabled) { + d->slotPropertyChanged_subType_enabled = false; + if (set["primaryKey"].value().toBool()==true + && property.value().toString()!=KexiDB::Field::typeString(KexiDB::Field::BigInteger)) + { + kexipluginsdbg << "INVALID " << property.value().toString() << endl; +// if (KMessageBox::Yes == KMessageBox::questionYesNo(this, msg, +// i18n("This field has promary key assigned. Setting autonumber field"), +// KGuiItem(i18n("Create &Primary Key"), "key"), KStdGuiItem::cancel() )) + + } + KexiDB::Field::Type type = KexiDB::intToFieldType( set["type"].value().toInt() ); + QString typeName; +/* disabled - "mime" is moved from subType to "objectType" custom property + if (type==KexiDB::Field::BLOB) { //special case + //find i18n'd text + QStringList stringsList, namesList; + getSubTypeListData(KexiDB::Field::BLOBGroup, stringsList, namesList); + const int stringIndex = stringsList.findIndex( property.value().toString() ); + if (-1 == stringIndex || stringIndex>=(int)namesList.count()) + typeName = property.value().toString(); //for sanity + else + typeName = namesList[stringIndex]; + } + else {*/ + typeName = KexiDB::Field::typeName( KexiDB::Field::typeForString(property.value().toString()) ); +// } +// kdDebug() << property.value().toString() << endl; +// kdDebug() << set["type"].value() << endl; +// if (KexiDB::Field::typeGroup( set["type"].value().toInt() ) == (int)KexiDB::Field::TextGroup) { + CommandGroup* changeFieldTypeCommand = new CommandGroup( + i18n("Change type for field \"%1\" to \"%2\"").arg(set["name"].value().toString()) + .arg(typeName) ); + d->setPropertyValueIfNeeded( set, "subType", property.value(), property.oldValue(), + changeFieldTypeCommand ); + + kexipluginsdbg << set["type"].value() << endl; + const KexiDB::Field::Type newType = KexiDB::Field::typeForString(property.value().toString()); + set["type"].setValue( newType ); + + // cast "defaultValue" property value to a new type + QVariant oldDefVal( set["defaultValue"].value() ); + QVariant newDefVal( tryCastQVariant(oldDefVal, KexiDB::Field::variantType(type)) ); + if (oldDefVal.type()!=newDefVal.type()) + set["defaultValue"].setType( newDefVal.type() ); + d->setPropertyValueIfNeeded( set, "defaultValue", newDefVal, newDefVal, + changeFieldTypeCommand ); + + d->updatePropertiesVisibility(newType, set); + //properties' visiblility changed: refresh prop. set + propertySetReloaded(true); + d->slotPropertyChanged_subType_enabled = true; + + addHistoryCommand( changeFieldTypeCommand, false /* !execute */ ); + return; +// } +// d->slotPropertyChanged_subType_enabled = true; +// return; + } + + if (d->addHistoryCommand_in_slotPropertyChanged_enabled && !changePrimaryKey/*we'll add multiple commands for PK*/) { + addHistoryCommand( new ChangeFieldPropertyCommand(this, set, + property.name(), property.oldValue() /* ??? */, property.value()), + false /* !execute */ ); + } + + if (changePrimaryKey) { + d->slotPropertyChanged_primaryKey_enabled = false; + if (setPrimaryKey) { + //primary key implies some rules + //const bool prev_addHistoryCommand_in_slotPropertyChanged_enabled = d->addHistoryCommand_in_slotPropertyChanged_enabled; +// d->addHistoryCommand_in_slotPropertyChanged_enabled = false; + + //this action contains subactions + CommandGroup *setPrimaryKeyCommand = new CommandGroup( + i18n("Set primary key for field \"%1\"") + .arg(set["name"].value().toString()) ); + if (toplevelCommand) + toplevelCommand->addCommand( setPrimaryKeyCommand ); + else + toplevelCommand = setPrimaryKeyCommand; + + d->setPropertyValueIfNeeded( set, "primaryKey", QVariant(true,1), setPrimaryKeyCommand, true /*forceAddCommand*/ ); + d->setPropertyValueIfNeeded( set, "unique", QVariant(true,1), setPrimaryKeyCommand ); + d->setPropertyValueIfNeeded( set, "notNull", QVariant(true,1), setPrimaryKeyCommand ); + d->setPropertyValueIfNeeded( set, "allowEmpty", QVariant(false,1), setPrimaryKeyCommand ); + d->setPropertyValueIfNeeded( set, "indexed", QVariant(true,1), setPrimaryKeyCommand ); +//! \todo: add setting for this: "Integer PKeys have autonumber set by default" + d->setPropertyValueIfNeeded( set, "autoIncrement", QVariant(true,1), setPrimaryKeyCommand ); + +/* set["unique"] = QVariant(true,1); + set["notNull"] = QVariant(true,1); + set["allowEmpty"] = QVariant(false,1); + set["indexed"] = QVariant(true,1); + set["autoIncrement"] = QVariant(true,1);*/ +// d->addHistoryCommand_in_slotPropertyChanged_enabled = prev_addHistoryCommand_in_slotPropertyChanged_enabled; +//down addHistoryCommand( toplevelCommand, false /* !execute */ ); + } + else {//! set PK to false + //remember this action containing 2 subactions + CommandGroup *setPrimaryKeyCommand = new CommandGroup( + i18n("Unset primary key for field \"%1\"") + .arg(set["name"].value().toString()) ); + if (toplevelCommand) + toplevelCommand->addCommand( setPrimaryKeyCommand ); + else + toplevelCommand = setPrimaryKeyCommand; + + d->setPropertyValueIfNeeded( set, "primaryKey", QVariant(false,1), setPrimaryKeyCommand, true /*forceAddCommand*/ ); + d->setPropertyValueIfNeeded( set, "autoIncrement", QVariant(false,1), setPrimaryKeyCommand ); +// set["autoIncrement"] = QVariant(false,1); + +//down addHistoryCommand( toplevelCommand, false /* !execute */ ); + } + switchPrimaryKey(set, setPrimaryKey, true/*wasPKey*/, toplevelCommand); + d->updatePropertiesVisibility( + KexiDB::Field::typeForString( set["subType"].value().toString() ), set, toplevelCommand); + addHistoryCommand( toplevelCommand, false /* !execute */ ); + //properties' visiblility changed: refresh prop. set + propertySetReloaded(true/*preservePrevSelection*/); + d->slotPropertyChanged_primaryKey_enabled = true; + } +} + +void KexiTableDesignerView::slotRowInserted() +{ + updateActions(); + + if (d->addHistoryCommand_in_slotRowInserted_enabled) { + const int row = d->view->currentRow(); + if (row>=0) { + addHistoryCommand( new InsertEmptyRowCommand( this, row ), false /* !execute */ ); + } + } + //TODO? +} + +void KexiTableDesignerView::slotAboutToDeleteRow( + KexiTableItem& item, KexiDB::ResultInfo* result, bool repaint) +{ + Q_UNUSED(result) + Q_UNUSED(repaint) + if (item[COLUMN_ID_ICON].toString()=="key") + d->primaryKeyExists = false; + + if (d->addHistoryCommand_in_slotAboutToDeleteRow_enabled) { + const int row = d->view->data()->findRef(&item); + KoProperty::Set *set = row >=0 ? d->sets->at(row) : 0; + //set can be 0 here, what means "removing empty row" + addHistoryCommand( + new RemoveFieldCommand( this, row, set ), + false /* !execute */ + ); + } +} + +KexiDB::Field * KexiTableDesignerView::buildField( const KoProperty::Set &set ) const +{ + //create a map of property values + kexipluginsdbg << set["type"].value() << endl; + QMap values = KoProperty::propertyValues(set); + //remove internal values, to avoid creating custom field's properties + QMap::Iterator it = values.begin(); + KexiDB::Field *field = new KexiDB::Field(); + + while (it!=values.end()) { + const QString propName( it.key() ); + if (d->internalPropertyNames.find(propName.latin1()) || propName.startsWith("this:") + || (/*sanity*/propName=="objectType" && KexiDB::Field::BLOB != KexiDB::intToFieldType( set["type"].value().toInt() ))) + { + QMap::Iterator it_tmp = it; + ++it; + values.remove(it_tmp); + } + else + ++it; + } + //assign properties to the field + // (note that "objectType" property will be saved as custom property) + if (!KexiDB::setFieldProperties( *field, values )) { + delete field; + return 0; + } + return field; +} + +tristate KexiTableDesignerView::buildSchema(KexiDB::TableSchema &schema, bool beSilent) +{ + if (!d->view->acceptRowEdit()) + return cancelled; + + tristate res = true; + //check for pkey; automatically add a pkey if user wanted + if (!d->primaryKeyExists) { + if (beSilent) { + kexipluginsdbg << "KexiTableDesignerView::buildSchema(): no primay key defined..." << endl; + } + else { + const int questionRes = KMessageBox::questionYesNoCancel(this, + i18n("

Table \"%1\" has no primary key defined.

" + "

Although a primary key is not required, it is needed " + "for creating relations between database tables. " + "Do you want to add primary key automatically now?

" + "

If you want to add a primary key by hand, press \"Cancel\" " + "to cancel saving table design.

").arg(schema.name()), + QString::null, KGuiItem(i18n("&Add Primary Key"), "key"), KStdGuiItem::no(), + "autogeneratePrimaryKeysOnTableDesignSaving"); + if (questionRes==KMessageBox::Cancel) { + return cancelled; + } + else if (questionRes==KMessageBox::Yes) { + //-find unique name, starting with, "id", "id2", .... + int i=0; + int idIndex = 1; //means "id" + QString pkFieldName("id%1"); + QString pkFieldCaption(i18n("Identifier%1", "Id%1")); + while (i<(int)d->sets->size()) { + KoProperty::Set *set = d->sets->at(i); + if (set) { + if ((*set)["name"].value().toString() + == pkFieldName.arg(idIndex==1?QString::null : QString::number(idIndex)) + || (*set)["caption"].value().toString() + == pkFieldCaption.arg(idIndex==1?QString::null : QString::number(idIndex))) + { + //try next id index + i = 0; + idIndex++; + continue; + } + } + i++; + } + pkFieldName = pkFieldName.arg(idIndex==1?QString::null : QString::number(idIndex)); + pkFieldCaption = pkFieldCaption.arg(idIndex==1?QString::null : QString::number(idIndex)); + //ok, add PK with such unique name + d->view->insertEmptyRow(0); + d->view->setCursorPosition(0, COLUMN_ID_CAPTION); + d->view->data()->updateRowEditBuffer(d->view->selectedItem(), COLUMN_ID_CAPTION, + QVariant(pkFieldCaption)); + d->view->data()->updateRowEditBuffer(d->view->selectedItem(), COLUMN_ID_TYPE, + QVariant(KexiDB::Field::IntegerGroup-1/*counting from 0*/)); + if (!d->view->data()->saveRowChanges(*d->view->selectedItem(), true)) { + return cancelled; + } + slotTogglePrimaryKey(); + } + } + } + + //check for duplicates + KoProperty::Set *b = 0; + bool no_fields = true; + int i; + QDict names(101, false); + char dummy; + for (i=0;i<(int)d->sets->size();i++) { + b = d->sets->at(i); + if (b) { + no_fields = false; + const QString name = (*b)["name"].value().toString(); + if (name.isEmpty()) { + if (beSilent) { + kexipluginswarn << + QString("KexiTableDesignerView::buildSchema(): no field caption entered at row %1...") + .arg(i+1) << endl; + } + else { + d->view->setCursorPosition(i, COLUMN_ID_CAPTION); + d->view->startEditCurrentCell(); + KMessageBox::information(this, i18n("You should enter field caption.") ); + } + res = cancelled; + break; + } + if (names[name]) { + break; + } + names.insert( name, &dummy ); //remember + } + } + if (res == true && no_fields) {//no fields added + if (beSilent) { + kexipluginswarn << + "KexiTableDesignerView::buildSchema(): no field defined..." << endl; + } + else { + KMessageBox::sorry(this, + i18n("You have added no fields.\nEvery table should have at least one field.") ); + } + res = cancelled; + } + if (res == true && b && i<(int)d->sets->size()) {//found a duplicate + if (beSilent) { + kexipluginswarn << + QString("KexiTableDesignerView::buildSchema(): duplicated field name '%1'") + .arg((*b)["name"].value().toString()) << endl; + } + else { + d->view->setCursorPosition(i, COLUMN_ID_CAPTION); + d->view->startEditCurrentCell(); +//! @todo for "names hidden" mode we won't get this error because user is unable to change names + KMessageBox::sorry(this, + i18n("You have added \"%1\" field name twice.\nField names cannot be repeated. " + "Correct name of the field.") + .arg((*b)["name"].value().toString()) ); + } + res = cancelled; + } + if (res == true) { + //for every field, create KexiDB::Field definition + for (i=0;i<(int)d->sets->size();i++) { + KoProperty::Set *s = d->sets->at(i); + if (!s) + continue; + KexiDB::Field * f = buildField( *s ); + if (!f) + continue; //hmm? + schema.addField(f); + if (!(*s)["rowSource"].value().toString().isEmpty() && !(*s)["rowSourceType"].value().toString().isEmpty()) { + //add lookup column + KexiDB::LookupFieldSchema *lookupFieldSchema = new KexiDB::LookupFieldSchema(); + lookupFieldSchema->rowSource().setTypeByName( (*s)["rowSourceType"].value().toString() ); + lookupFieldSchema->rowSource().setName( (*s)["rowSource"].value().toString() ); + lookupFieldSchema->setBoundColumn( (*s)["boundColumn"].value().toInt() ); +//! @todo this is backward-compatible code for "single visible column" implementation +//! for multiple columns, only the first is displayed, so there is a data loss is GUI is used +//! -- special koproperty editor needed + QValueList visibleColumns; + const int visibleColumn = (*s)["visibleColumn"].value().toInt(); + if (visibleColumn >= 0) + visibleColumns.append( (uint)visibleColumn ); + lookupFieldSchema->setVisibleColumns( visibleColumns ); +//! @todo support columnWidths(), columnHeadersVisible(), maximumListRows(), limitToList(), displayWidget() + if (!schema.setLookupFieldSchema(f->name(), lookupFieldSchema)) { + kexipluginswarn << + "KexiTableDesignerView::buildSchema(): !schema.setLookupFieldSchema()" << endl; + delete lookupFieldSchema; + return false; + } + } + } + } + return res; +} + +//! @internal +//! A recursive function for copying alter table actions from undo/redo commands. +static void copyAlterTableActions(KCommand* command, KexiDB::AlterTableHandler::ActionList &actions) +{ + CommandGroup* cmdGroup = dynamic_cast( command ); + if (cmdGroup) {//command group: flatten it + for (QPtrListIterator it(cmdGroup->commands()); it.current(); ++it) + copyAlterTableActions(it.current(), actions); + return; + } + Command* cmd = dynamic_cast( command ); + if (!cmd) { + kexipluginswarn << "KexiTableDesignerView::copyAlterTableActions(): cmd is not of type 'Command'!" << endl; + return; + } + KexiDB::AlterTableHandler::ActionBase* action = cmd->createAction(); + //some commands can contain null actions, e.g. "set visibility" command + if (action) + actions.append( action ); +} + +tristate KexiTableDesignerView::buildAlterTableActions(KexiDB::AlterTableHandler::ActionList &actions) +{ + actions.clear(); + kexipluginsdbg << "KexiTableDesignerView::buildAlterTableActions(): " << d->history->commands().count() + << " top-level command(s) to process..." << endl; + for (QPtrListIterator it(d->history->commands()); it.current(); ++it) { + copyAlterTableActions(it.current(), actions); + } + return true; +} + +KexiDB::SchemaData* KexiTableDesignerView::storeNewData(const KexiDB::SchemaData& sdata, bool &cancel) +{ + if (tempData()->table || m_dialog->schemaData()) //must not be + return 0; + + //create table schema definition + tempData()->table = new KexiDB::TableSchema(sdata.name()); + tempData()->table->setName( sdata.name() ); + tempData()->table->setCaption( sdata.caption() ); + tempData()->table->setDescription( sdata.description() ); + + tristate res = buildSchema(*tempData()->table); + cancel = ~res; + + //FINALLY: create table: + if (res == true) { + //todo + KexiDB::Connection *conn = mainWin()->project()->dbConnection(); + res = conn->createTable(tempData()->table); + if (res!=true) + parentDialog()->setStatus(conn, ""); + } + + if (res == true) { + //we've current schema + tempData()->tableSchemaChangedInPreviousView = true; +//not needed; KexiProject emits newItemStored signal //let project know the table is created +// mainWin()->project()->emitTableCreated(*tempData()->table); + } + else { + delete tempData()->table; + tempData()->table = 0; + } + return tempData()->table; +} + +tristate KexiTableDesignerView::storeData(bool dontAsk) +{ + if (!tempData()->table || !m_dialog->schemaData()) { + d->recentResultOfStoreData = false; + return false; + } + + KexiDB::Connection *conn = mainWin()->project()->dbConnection(); + KexiDB::AlterTableHandler *alterTableHandler = 0; + KexiDB::TableSchema *newTable = 0; + + //- create action list for the alter table handler + KexiDB::AlterTableHandler::ActionList actions; + tristate res = buildAlterTableActions( actions ); + bool realAlterTableCanBeUsed = false; //!< @todo this is temporary flag before we switch entirely to real alter table + if (res == true) { + alterTableHandler = new KexiDB::AlterTableHandler( *conn ); + alterTableHandler->setActions(actions); + + if (!d->tempStoreDataUsingRealAlterTable) { + //only compute requirements + KexiDB::AlterTableHandler::ExecutionArguments args; + args.onlyComputeRequirements = true; + (void)alterTableHandler->execute(tempData()->table->name(), args); + res = args.result; + if (res == true && 0 == (args.requirements & (0xffff ^ KexiDB::AlterTableHandler::SchemaAlteringRequired))) + realAlterTableCanBeUsed = true; + } + } + + if (res == true) { + res = KexiTablePart::askForClosingObjectsUsingTableSchema( + this, *conn, *tempData()->table, + i18n("You are about to change the design of table \"%1\" " + "but following objects using this table are opened:") + .arg(tempData()->table->name())); + } + + if (res == true) { + if (!d->tempStoreDataUsingRealAlterTable && !realAlterTableCanBeUsed) { +//! @todo temp; remove this case: + delete alterTableHandler; + alterTableHandler = 0; + // - inform about removing the current table and ask for confirmation + if (!d->dontAskOnStoreData && !dontAsk) { + bool emptyTable; + const QString msg = d->messageForSavingChanges(emptyTable); + if (!emptyTable) { + if (KMessageBox::No == KMessageBox::questionYesNo(this, msg)) + res = cancelled; + } + } + d->dontAskOnStoreData = false; //one-time use + if (~res) { + d->recentResultOfStoreData = res; + return res; + } + // keep old behaviour: + newTable = new KexiDB::TableSchema(); + // copy the schema data + static_cast(*newTable) = static_cast(*tempData()->table); + res = buildSchema(*newTable); + kexipluginsdbg << "KexiTableDesignerView::storeData() : BUILD SCHEMA:" << endl; + newTable->debug(); + + res = conn->alterTable(*tempData()->table, *newTable); + if (res != true) + parentDialog()->setStatus(conn, ""); + } + else { + KexiDB::AlterTableHandler::ExecutionArguments args; + newTable = alterTableHandler->execute(tempData()->table->name(), args); + res = args.result; + kexipluginsdbg << "KexiTableDesignerView::storeData() : ALTER TABLE EXECUTE: " + << res.toString() << endl; + if (true != res) { + alterTableHandler->debugError(); + parentDialog()->setStatus(alterTableHandler, ""); + } + } + } + if (res == true) { + //change current schema + tempData()->table = newTable; + tempData()->tableSchemaChangedInPreviousView = true; + d->history->clear(); + } + else { + delete newTable; + } + delete alterTableHandler; + d->recentResultOfStoreData = res; + return res; +} + +tristate KexiTableDesignerView::simulateAlterTableExecution(QString *debugTarget) +{ +#ifndef KEXI_NO_UNDOREDO_ALTERTABLE +# ifdef KEXI_DEBUG_GUI + if (mainWin()->activeWindow() != parentDialog()) //to avoid executing for multiple alter table views + return false; + if (!tempData()->table || !m_dialog->schemaData()) + return false; + KexiDB::Connection *conn = mainWin()->project()->dbConnection(); + KexiDB::AlterTableHandler::ActionList actions; + tristate res = buildAlterTableActions( actions ); +//todo: result? + KexiDB::AlterTableHandler alterTableHandler( *conn ); + alterTableHandler.setActions(actions); + KexiDB::AlterTableHandler::ExecutionArguments args; + if (debugTarget) { + args.debugString = debugTarget; + } + else { + args.simulate = true; + } + (void)alterTableHandler.execute(tempData()->table->name(), args); + return args.result; +# else + return false; +# endif +#else + return false; +#endif +} + +void KexiTableDesignerView::slotSimulateAlterTableExecution() +{ + (void)simulateAlterTableExecution(0); +} + +tristate KexiTableDesignerView::executeRealAlterTable() +{ + QSignal signal; + signal.connect( mainWin(), SLOT(slotProjectSave()) ); + d->tempStoreDataUsingRealAlterTable = true; + d->recentResultOfStoreData = false; + signal.activate(); //will call KexiMainWindowImpl::slotProjectSaveAs() and thus storeData() + d->tempStoreDataUsingRealAlterTable = false; + return d->recentResultOfStoreData; +} + +KexiTablePart::TempData* KexiTableDesignerView::tempData() const +{ + return static_cast(parentDialog()->tempData()); +} + +/*void KexiTableDesignerView::slotAboutToUpdateRow( + KexiTableItem* item, KexiDB::RowEditBuffer* buffer, KexiDB::ResultInfo* result) +{ + KexiDB::RowEditBuffer::SimpleMap map = buffer->simpleBuffer(); + buffer->debug(); + + QVariant old_type = item->at(1); + QVariant *buf_type = buffer->at( d->view->field(1)->name() ); + + //check if there is a type specified +// if ((old_type.isNull() && !buf_type) || (buf_type && buf_type->isNull())) { + //kdDebug() << "err" << endl; + //} +// allow = true; +// m_dirty = m_dirty | result->success; +}*/ + +#ifdef KEXI_DEBUG_GUI +void KexiTableDesignerView::debugCommand( KCommand* command, int nestingLevel ) +{ + if (dynamic_cast(command)) + KexiUtils::addAlterTableActionDebug(dynamic_cast(command)->debugString(), nestingLevel); + else + KexiUtils::addAlterTableActionDebug(command->name(), nestingLevel); + //show subcommands + if (dynamic_cast(command)) { + for (QPtrListIterator it(dynamic_cast(command)->commands()); it.current(); ++it) { + debugCommand(it.current(), nestingLevel + 1); + } + } +} +#endif + +void KexiTableDesignerView::addHistoryCommand( KCommand* command, bool execute ) +{ +#ifndef KEXI_NO_UNDOREDO_ALTERTABLE +# ifdef KEXI_DEBUG_GUI + debugCommand( command, 0 ); +# endif + d->history->addCommand( command, execute ); + updateUndoRedoActions(); +#endif +} + +void KexiTableDesignerView::updateUndoRedoActions() +{ +#ifndef KEXI_NO_UNDOREDO_ALTERTABLE + setAvailable("edit_undo", d->historyActionCollection->action("edit_undo")->isEnabled()); + setAvailable("edit_redo", d->historyActionCollection->action("edit_redo")->isEnabled()); +#endif +} + +void KexiTableDesignerView::slotUndo() +{ +#ifndef KEXI_NO_UNDOREDO_ALTERTABLE +# ifdef KEXI_DEBUG_GUI + KexiUtils::addAlterTableActionDebug(QString("UNDO:")); +# endif + d->history->undo(); + updateUndoRedoActions(); +#endif +} + +void KexiTableDesignerView::slotRedo() +{ +#ifndef KEXI_NO_UNDOREDO_ALTERTABLE +# ifdef KEXI_DEBUG_GUI + KexiUtils::addAlterTableActionDebug(QString("REDO:")); +# endif + d->history->redo(); + updateUndoRedoActions(); +#endif +} + +void KexiTableDesignerView::slotCommandExecuted(KCommand *command) +{ +#ifdef KEXI_DEBUG_GUI + debugCommand( command, 1 ); +#endif +} + +void KexiTableDesignerView::slotAboutToShowContextMenu() +{ + //update title + if (propertySet()) { + const KoProperty::Set &set = *propertySet(); + QString captionOrName(set["caption"].value().toString()); + if (captionOrName.isEmpty()) + captionOrName = set["name"].value().toString(); +//! @todo show "field" icon + d->contextMenuTitle->setTitle( i18n("Table field \"%1\"").arg(captionOrName) ); + } + else { + d->contextMenuTitle->setTitle( i18n("Empty table row", "Empty Row") ); + } +} + +QString KexiTableDesignerView::debugStringForCurrentTableSchema(tristate& result) +{ + KexiDB::TableSchema tempTable; + //copy schema data + static_cast(tempTable) = static_cast(*tempData()->table); + result = buildSchema(tempTable, true /*beSilent*/); + if (true!=result) + return QString::null; + return tempTable.debugString(false /*without name*/); +} + +// -- low-level actions used by undo/redo framework + +void KexiTableDesignerView::clearRow(int row, bool addCommand) +{ + if (!d->view->acceptRowEdit()) + return; + KexiTableItem *item = d->view->itemAt(row); + if (!item) + return; + //remove from prop. set + d->sets->remove( row ); + //clear row in table view (just clear value in COLUMN_ID_TYPE column) +// for (int i=0; i < (int)d->view->data()->columnsCount(); i++) { + if (!addCommand) { + d->addHistoryCommand_in_slotRowUpdated_enabled = false; + d->addHistoryCommand_in_slotPropertyChanged_enabled = false; + d->slotBeforeCellChanged_enabled = false; + } + d->view->data()->updateRowEditBuffer(item, COLUMN_ID_TYPE, QVariant()); + if (!addCommand) { + d->addHistoryCommand_in_slotRowUpdated_enabled = true; + d->addHistoryCommand_in_slotPropertyChanged_enabled = true; + d->slotBeforeCellChanged_enabled = true; + } + d->view->data()->saveRowChanges(*item, true); +} + +void KexiTableDesignerView::insertField(int row, const QString& caption, bool addCommand) +{ + insertFieldInternal(row, 0, caption, addCommand); +} + +void KexiTableDesignerView::insertField(int row, KoProperty::Set& set, bool addCommand) +{ + insertFieldInternal(row, &set, QString::null, addCommand); +} + +void KexiTableDesignerView::insertFieldInternal(int row, KoProperty::Set* set, //const KexiDB::Field& field, + const QString& caption, bool addCommand) +{ + if (set && (!set->contains("type") || !set->contains("caption"))) { + kexipluginswarn << "KexiTableDesignerView::insertField(): no 'type' or 'caption' property in set!" << endl; + return; + } + if (!d->view->acceptRowEdit()) + return; + KexiTableItem *item = d->view->itemAt(row); + if (!item) + return; + if (!addCommand) { + d->addHistoryCommand_in_slotRowUpdated_enabled = false; + d->addHistoryCommand_in_slotPropertyChanged_enabled = false; + d->slotBeforeCellChanged_enabled = false; + } + d->view->data()->updateRowEditBuffer(item, COLUMN_ID_CAPTION, + set ? (*set)["caption"].value() : QVariant(caption));//field.caption()); + d->view->data()->updateRowEditBuffer(item, COLUMN_ID_TYPE, + set ? (int)KexiDB::Field::typeGroup( (*set)["type"].value().toInt() )-1/*counting from 0*/ + : (((int)KexiDB::Field::TextGroup)-1)/*default type, counting from 0*/ + ); + d->view->data()->updateRowEditBuffer(item, COLUMN_ID_DESC, + set ? (*set)["description"].value() : QVariant());//field.description()); + if (!addCommand) { + d->slotBeforeCellChanged_enabled = true; + } + //this will create a new property set: + d->view->data()->saveRowChanges(*item); + if (set) { + KoProperty::Set *newSet = d->sets->at(row); + if (newSet) { + *newSet = *set; //deep copy + } + else { + kexipluginswarn << "KexiTableDesignerView::insertField() !newSet, row==" << row << endl; + } + } + if (!addCommand) { + d->addHistoryCommand_in_slotPropertyChanged_enabled = true; + d->addHistoryCommand_in_slotRowUpdated_enabled = true; + } + d->view->updateRow( row ); + propertySetReloaded(true); +} + +void KexiTableDesignerView::insertEmptyRow( int row, bool addCommand ) +{ + if (!addCommand) { + d->addHistoryCommand_in_slotRowInserted_enabled = false; + } + d->view->insertEmptyRow( row ); + if (!addCommand) { + d->addHistoryCommand_in_slotRowInserted_enabled = true; + } +} + +/*void KexiTableDesignerView::deleteRow( int row ) +{ + d->addHistoryCommand_in_slotAboutToDeleteRow_enabled = false; + d->view->deleteItem( d->view->data()->at(row) ); + d->addHistoryCommand_in_slotAboutToDeleteRow_enabled = true; +}*/ + +void KexiTableDesignerView::deleteRow( int row, bool addCommand ) +{ + KexiTableItem *item = d->view->itemAt( row ); + if (!item) + return; + if (!addCommand) { + d->addHistoryCommand_in_slotAboutToDeleteRow_enabled = false; + } + const bool res = d->view->deleteItem(item); + if (!addCommand) { + d->addHistoryCommand_in_slotAboutToDeleteRow_enabled = true; + } + if (!res) + return; +} + +void KexiTableDesignerView::changeFieldPropertyForRow( int row, + const QCString& propertyName, const QVariant& newValue, + KoProperty::Property::ListData* const listData, bool addCommand ) +{ +#ifdef KEXI_DEBUG_GUI + KexiUtils::addAlterTableActionDebug(QString("** changeFieldProperty: \"") + + QString(propertyName) + "\" to \"" + newValue.toString() + "\"", 2/*nestingLevel*/); +#endif + if (!d->view->acceptRowEdit()) + return; + + KoProperty::Set* set = d->sets->at( row ); + if (!set || !set->contains(propertyName)) + return; + KoProperty::Property &property = set->property(propertyName); + if (listData) { + if (listData->keys.isEmpty()) + property.setListData( 0 ); + else + property.setListData( new KoProperty::Property::ListData(*listData) ); + } + if (propertyName != "type") //delayed type update (we need to have subtype set properly) + property.setValue(newValue); + KexiTableItem *item = d->view->itemAt(row); + Q_ASSERT(item); + + if (propertyName == "type") { + // d->addHistoryCommand_in_slotRowUpdated_enabled = false; +// d->addHistoryCommand_in_slotPropertyChanged_enabled = false; + d->slotPropertyChanged_subType_enabled = false; + d->view->data()->updateRowEditBuffer(item, COLUMN_ID_TYPE, + int( KexiDB::Field::typeGroup( newValue.toInt() ) )-1); + d->view->data()->saveRowChanges(*item); + d->addHistoryCommand_in_slotRowUpdated_enabled = true; +// d->addHistoryCommand_in_slotPropertyChanged_enabled = true; + // d->slotPropertyChanged_subType_enabled = true; + property.setValue(newValue); //delayed type update (we needed to have subtype set properly) + } + + if (!addCommand) { + d->addHistoryCommand_in_slotRowUpdated_enabled = false; + d->addHistoryCommand_in_slotPropertyChanged_enabled = false; + d->slotPropertyChanged_subType_enabled = false; + } + //special cases: properties displayed within the data grid: + if (propertyName == "caption") { + if (!addCommand) { + d->slotBeforeCellChanged_enabled = false; + } + d->view->data()->updateRowEditBuffer(item, COLUMN_ID_CAPTION, newValue); + d->view->data()->saveRowChanges(*item); + if (!addCommand) { + d->slotBeforeCellChanged_enabled = true; + } + } + else if (propertyName == "description") { + if (!addCommand) { + d->slotBeforeCellChanged_enabled = false; + } + d->view->data()->updateRowEditBuffer(item, COLUMN_ID_DESC, newValue); + if (!addCommand) { + d->slotBeforeCellChanged_enabled = true; + } + d->view->data()->saveRowChanges(*item); + } + if (!addCommand) { + d->addHistoryCommand_in_slotPropertyChanged_enabled = true; + d->addHistoryCommand_in_slotRowUpdated_enabled = true; + d->slotPropertyChanged_subType_enabled = true; + } + d->view->updateRow( row ); +} + +void KexiTableDesignerView::changeFieldProperty( int fieldUID, + const QCString& propertyName, const QVariant& newValue, + KoProperty::Property::ListData* const listData, bool addCommand ) +{ + //find a property by UID + const int row = d->sets->findRowForPropertyValue("uid", fieldUID); + if (row<0) { + kexipluginswarn << "KexiTableDesignerView::changeFieldProperty(): field with uid="<view->acceptRowEdit()) + return; + + //find a property by name + const int row = d->sets->findRowForPropertyValue("uid", fieldUID); + if (row<0) + return; + KoProperty::Set* set = d->sets->at( row ); + if (!set || !set->contains(propertyName)) + return; + + KoProperty::Property &property = set->property(propertyName); + if (property.isVisible() != visible) { + property.setVisible(visible); + propertySetReloaded(true); + } +} + +void KexiTableDesignerView::propertySetSwitched() +{ + KexiDataTable::propertySetSwitched(); + + //if (parentDialog()!=parentDialog()->mainWin()->currentDialog()) + // return; //this is not the current dialog's view + + static_cast(parentDialog()->part())->lookupColumnPage() + ->assignPropertySet(propertySet()); +} + +bool KexiTableDesignerView::isPhysicalAlteringNeeded() +{ + //- create action list for the alter table handler + KexiDB::AlterTableHandler::ActionList actions; + tristate res = buildAlterTableActions( actions ); + if (res != true) + return true; + + KexiDB::Connection *conn = mainWin()->project()->dbConnection(); + KexiDB::AlterTableHandler *alterTableHandler = new KexiDB::AlterTableHandler( *conn ); + alterTableHandler->setActions(actions); + + //only compute requirements + KexiDB::AlterTableHandler::ExecutionArguments args; + args.onlyComputeRequirements = true; + (void)alterTableHandler->execute(tempData()->table->name(), args); + res = args.result; + delete alterTableHandler; + if (res == true && 0 == (args.requirements & (0xffff ^ KexiDB::AlterTableHandler::SchemaAlteringRequired))) + return false; + return true; +} + +#include "kexitabledesignerview.moc" diff --git a/kexi/plugins/tables/kexitabledesignerview.h b/kexi/plugins/tables/kexitabledesignerview.h new file mode 100644 index 00000000..773163b6 --- /dev/null +++ b/kexi/plugins/tables/kexitabledesignerview.h @@ -0,0 +1,258 @@ +/* This file is part of the KDE project + Copyright (C) 2004-2007 Jaroslaw Staniek + + 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 KEXITABLEDESIGNERINTERVIEW_H +#define KEXITABLEDESIGNERINTERVIEW_H + +#include +#include +#include + +#include +#include "kexitablepart.h" + +class KexiTableItem; +class KexiTableDesignerViewPrivate; +class KCommand; +class CommandGroup; + +namespace KoProperty { + class Set; +} + +//! Design view of the Table Designer +/*! Contains a spreadsheet-like space for entering field definitions. + Property editor is provided for altering field definitions. + + The view also supports Undo and Redo operations. + These are connected to a factility creating a list of actions used + by AlterTableHandler to perform required operation of altering the table. + + Altering itself is performed upon design saving (storeData()). + Saving unstored designs just creates a new table. + Saving changes made to empty (not filled with data) table is performed + by physically deleting the previous table schema and recreating it + TODO: this will be not quite when we have db relationships supported. + + Saving changes made to table containing data requires use of the AlterTableHandler + functionality. +*/ +class KexiTableDesignerView : public KexiDataTable, public KexiTableDesignerInterface +{ + Q_OBJECT + + public: + /*! Creates a new alter table dialog. */ + KexiTableDesignerView(KexiMainWindow *win, QWidget *parent); + + virtual ~KexiTableDesignerView(); + + KexiTablePart::TempData* tempData() const; + + /*! Clears field information entered for row. + This is performed by removing values from caption and data type columns. + Used by InsertFieldCommand to undo inserting a new field. */ + virtual void clearRow(int row, bool addCommand = false); + + /*! Inserts a new field with \a caption for \a row. + Property set is also created. */ + virtual void insertField(int row, const QString& caption, bool addCommand = false); + + /*! Inserts a new \a field for \a row. + Property set is also created. \a set will be deeply-copied into the new set. + Used by InsertFieldCommand to insert a new field. */ + virtual void insertField(int row, KoProperty::Set& set, bool addCommand = false); + + /*! Inserts a new empty row at position \a row. + Used by RemoveFieldCommand as a part of undo inserting a new field; + also used by InsertEmptyRowCommand. */ + virtual void insertEmptyRow( int row, bool addCommand = false ); + + /*! Deletes \a row from the table view. Property set is also deleted. + All the subsequent fields are moved up. Used for undoing InsertEmptyRowCommand + and by RemoveFieldCommand to remove a field. */ + virtual void deleteRow( int row, bool addCommand = false ); + + /*! Deletes a field for \a row. Property set is also deleted. + Used by RemoveFieldCommand to remove a field. */ +// virtual void deleteField( int row ); + + /*! Changes property \a propertyName to \a newValue for a field at row \a row. + If \a listData is not NULL and not empty, a deep copy of it is passed to Property::setListData(). + If \a listData \a nlist if not NULL but empty, Property::setListData(0) is called. */ + virtual void changeFieldPropertyForRow( int row, + const QCString& propertyName, const QVariant& newValue, + KoProperty::Property::ListData* const listData, bool addCommand ); + + /*! Changes property \a propertyName to \a newValue. + Works exactly like changeFieldPropertyForRow() except the field is pointed by \a fieldUID. + Used by ChangeFieldPropertyCommand to change field's property. */ + void changeFieldProperty( int fieldUID, const QCString& propertyName, + const QVariant& newValue, KoProperty::Property::ListData* const listData = 0, + bool addCommand = false ); + + /*! Changes visibility of property \a propertyName to \a visible for a field pointed by \a fieldUID. + Used by ChangePropertyVisibilityCommand. */ + void changePropertyVisibility( int fieldUID, const QCString& propertyName, bool visible ); + + /*! Builds table field's schema by looking at the \a set. */ + KexiDB::Field * buildField( const KoProperty::Set &set ) const; + + /*! Creates temporary table for the current design and returns debug string for it. */ + virtual QString debugStringForCurrentTableSchema(tristate& result); + + /*! Simulates execution of alter table, and puts debug into \a debugTarget. + A case when debugTarget is not 0 is true for the alter table test suite. */ + virtual tristate simulateAlterTableExecution(QString *debugTarget); + + public slots: + /*! Real execution of the Alter Table. For debugging of the real alter table. + \return true on success, false on failure and cancelled if user has cancelled + execution. */ + virtual tristate executeRealAlterTable(); + + protected slots: + /*! Equivalent to updateActions(false). Called on row insert/delete + in a KexiDataAwarePropertySet. */ + void updateActions(); + + virtual void slotUpdateRowActions(int row); + + void slotAboutToShowContextMenu(); + + //! Called before cell change in tableview. + void slotBeforeCellChanged(KexiTableItem *item, int colnum, + QVariant& newValue, KexiDB::ResultInfo* result); + + //! Called on row change in a tableview. + void slotRowUpdated(KexiTableItem *item); + + //! Called before row inserting in tableview. + void slotRowInserted(); +// void slotAboutToInsertRow(KexiTableItem* item, KexiDB::ResultInfo* result, bool repaint); + + //! Called before row deleting in tableview. + void slotAboutToDeleteRow(KexiTableItem& item, KexiDB::ResultInfo* result, bool repaint); + + /*! Called after any property has been changed in the current property set, + to perform some actions (like updating other dependent properties) */ + void slotPropertyChanged(KoProperty::Set& set, KoProperty::Property& property); + + /*! Toggles primary key for currently selected field. + Does nothing for empty row. */ + void slotTogglePrimaryKey(); + + /*! Undoes the recently performed action. */ + void slotUndo(); + + /*! Redoes the recently undoed action. */ + void slotRedo(); + + /*! Reaction on command execution from the command history */ + void slotCommandExecuted(KCommand *command); + + /*! Simulates real execution of the Alter Table. For debugging. */ + void slotSimulateAlterTableExecution(); + + protected: + virtual void updateActions(bool activated); + + //! called whenever data should be reloaded (on switching to this view mode) + void initData(); + + /*! Creates a new property set for \a field. + The property set will be asigned to \a row, and owned by this dialog. + If \a newOne is true, the property set will be marked as newly created. + \return newly created property set. */ + KoProperty::Set* createPropertySet( int row, const KexiDB::Field& field, bool newOne = false ); + + virtual tristate beforeSwitchTo(int mode, bool &dontStore); + + virtual tristate afterSwitchFrom(int mode); + + /*! \return property set associated with currently selected row (i.e. field) + or 0 if current row is empty. */ + virtual KoProperty::Set *propertySet(); + +// void removeCurrentPropertySet(); + + /*! Reimplemented from KexiViewBase, because tables creation is more complex. + No table schema altering is required, so just buildSchema() is used to create a new schema. + */ + virtual KexiDB::SchemaData* storeNewData(const KexiDB::SchemaData& sdata, bool &cancel); + + /*! Reimplemented from KexiViewBase, because table storage is more complex. + Table schema altering may be required, so just buildSchema() is used to create a new schema. + */ + virtual tristate storeData(bool dontAsk = false); + + /*! Builds table schema by looking at the current design. Used in storeNewData() + and storeData(). + If \a beSilent is true, no message boxes are used to show questions or warnings. + This is used in the altertable test suite (kexi/tests/altertable). + \return true on successful schema creating, false on failure and cancelled when there + was a problem with user's design (and user has been informed about it). */ + tristate buildSchema(KexiDB::TableSchema &schema, bool beSilent = false); + + /*! Builds action list usable for KexiDB::AlterTableHandler by looking at undo buffer + of commands' history. Used in storeData() */ + tristate buildAlterTableActions(KexiDB::AlterTableHandler::ActionList &actions); + + /*! Helper, used for slotTogglePrimaryKey() and slotPropertyChanged(). + Assigns primary key icon and value for property set \a propertySet, + and deselects it from previous pkey's row. + \a aWasPKey is internal. + If \a commandGroup is not 0, it is used as parent group for storing actions' history. */ + void switchPrimaryKey(KoProperty::Set &propertySet, bool set, bool aWasPKey = false, + CommandGroup* commandGroup = 0); + + //! Gets subtype strings and names for type \a fieldType. + void getSubTypeListData(KexiDB::Field::TypeGroup fieldTypeGroup, + QStringList& stringsList, QStringList& namesList); + + /*! Adds history command \a command to the undo/redo buffer. + If \a execute is true, the command is executed afterwards. */ + void addHistoryCommand( KCommand* command, bool execute ); + + //! Updates undo/redo shared actions availability by looking at command history's action + void updateUndoRedoActions(); + +#ifdef KEXI_DEBUG_GUI + void debugCommand( KCommand* command, int nestingLevel ); +#endif + + /*! Inserts a new \a field for \a row. + Property set is also created. If \a set is not 0 (the default), + it will be copied into the new set. Used by insertField(). */ + void insertFieldInternal(int row, KoProperty::Set* set, const QString& caption, bool addCommand); + + //! Reimplemented to pass the information also to the "Lookup" tab + virtual void propertySetSwitched(); + + /*! \return true if physical altering is needed for the current list of actions. + Used in KexiTableDesignerView::beforeSwitchTo() to avoid warning about removinf + table data if table recreating is not needed. + True is also returned if there is any trouble with getting the answer. */ + bool isPhysicalAlteringNeeded(); + + private: + KexiTableDesignerViewPrivate *d; +}; + +#endif diff --git a/kexi/plugins/tables/kexitabledesignerview_p.cpp b/kexi/plugins/tables/kexitabledesignerview_p.cpp new file mode 100644 index 00000000..56ef997d --- /dev/null +++ b/kexi/plugins/tables/kexitabledesignerview_p.cpp @@ -0,0 +1,294 @@ +/* This file is part of the KDE project + Copyright (C) 2004-2007 Jaroslaw Staniek + + 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 "kexitabledesignerview_p.h" +#include "kexitabledesignerview.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "kexitabledesignercommands.h" + +using namespace KexiTableDesignerCommands; + +//---------------------------------------------- + +CommandHistory::CommandHistory(KActionCollection *actionCollection, bool withMenus) + : KCommandHistory(actionCollection, withMenus) +{ + // We need ALL the commands because we'll collect reuse their + // data before performing alter table, so set that to the maximum, + // as KCommandHistory has default = 50. + setUndoLimit(INT_MAX); + setRedoLimit(INT_MAX); +} + +void CommandHistory::addCommand(KCommand *command, bool execute) +{ + KCommandHistory::addCommand(command, execute); + m_commandsToUndo.append(command); +} + +void CommandHistory::undo() +{ + if (!m_commandsToUndo.isEmpty()) { + KCommand * cmd = m_commandsToUndo.take( m_commandsToUndo.count()-1 ); + m_commandsToRedo.append( cmd ); + } + KCommandHistory::undo(); +} + +void CommandHistory::redo() +{ + if (!m_commandsToRedo.isEmpty()) { + KCommand * cmd = m_commandsToRedo.take( m_commandsToRedo.count()-1 ); + m_commandsToUndo.append( cmd ); + } + KCommandHistory::redo(); +} + +void CommandHistory::clear() { + KCommandHistory::clear(); m_commandsToUndo.clear(); +} + +//---------------------------------------------- + +KexiTableDesignerViewPrivate::KexiTableDesignerViewPrivate(KexiTableDesignerView* aDesignerView) + : designerView(aDesignerView) + , sets(0) + , uniqueIdCounter(0) + , dontAskOnStoreData(false) + , slotTogglePrimaryKeyCalled(false) + , primaryKeyExists(false) + , slotPropertyChanged_primaryKey_enabled(true) + , slotPropertyChanged_subType_enabled(true) + , addHistoryCommand_in_slotPropertyChanged_enabled(true) + , addHistoryCommand_in_slotRowUpdated_enabled(true) + , addHistoryCommand_in_slotAboutToDeleteRow_enabled(true) + , addHistoryCommand_in_slotRowInserted_enabled(true) + , slotBeforeCellChanged_enabled(true) + , tempStoreDataUsingRealAlterTable(false) +{ + historyActionCollection = new KActionCollection((QWidget*)0,""); + history = new CommandHistory(historyActionCollection, true); + + internalPropertyNames.insert("subType",(char*)1); + internalPropertyNames.insert("uid",(char*)1); + internalPropertyNames.insert("newrow",(char*)1); + internalPropertyNames.insert("rowSource",(char*)1); + internalPropertyNames.insert("rowSourceType",(char*)1); + internalPropertyNames.insert("boundColumn",(char*)1); + internalPropertyNames.insert("visibleColumn",(char*)1); +} + +KexiTableDesignerViewPrivate::~KexiTableDesignerViewPrivate() { + delete sets; + delete historyActionCollection; + delete history; +} + +int KexiTableDesignerViewPrivate::generateUniqueId() +{ + return ++uniqueIdCounter; +} + +void KexiTableDesignerViewPrivate::setPropertyValueIfNeeded( + const KoProperty::Set& set, const QCString& propertyName, + const QVariant& newValue, const QVariant& oldValue, CommandGroup* commandGroup, + bool forceAddCommand, bool rememberOldValue, + QStringList* const slist, QStringList* const nlist) +{ + KoProperty::Property& property = set[propertyName]; + + KoProperty::Property::ListData *oldListData = property.listData() ? + new KoProperty::Property::ListData(*property.listData()) : 0; //remember because we'll change list data soon + if (slist && nlist) { + if (slist->isEmpty() || nlist->isEmpty()) { + property.setListData(0); + } + else { + property.setListData(*slist, *nlist); + } + } + if (oldValue.type() == newValue.type() + && (oldValue == newValue || (!oldValue.isValid() && !newValue.isValid())) + && !forceAddCommand) + { + return; + } + + const bool prev_addHistoryCommand_in_slotPropertyChanged_enabled + = addHistoryCommand_in_slotPropertyChanged_enabled; //remember + addHistoryCommand_in_slotPropertyChanged_enabled = false; + if (property.value() != newValue) + property.setValue( newValue, rememberOldValue ); + if (commandGroup) { + commandGroup->addCommand( + new ChangeFieldPropertyCommand( designerView, set, propertyName, oldValue, newValue, + oldListData, property.listData()) ); + } + delete oldListData; + addHistoryCommand_in_slotPropertyChanged_enabled + = prev_addHistoryCommand_in_slotPropertyChanged_enabled; //restore +} + +void KexiTableDesignerViewPrivate::setPropertyValueIfNeeded( + const KoProperty::Set& set, const QCString& propertyName, + const QVariant& newValue, CommandGroup* commandGroup, + bool forceAddCommand, bool rememberOldValue, + QStringList* const slist, QStringList* const nlist) +{ + KoProperty::Property& property = set[propertyName]; + QVariant oldValue( property.value() ); + setPropertyValueIfNeeded( set, propertyName, newValue, property.value(), + commandGroup, forceAddCommand, rememberOldValue, slist, nlist); +} + +void KexiTableDesignerViewPrivate::setVisibilityIfNeeded( const KoProperty::Set& set, KoProperty::Property* prop, + bool visible, bool &changed, CommandGroup *commandGroup ) +{ + if (prop->isVisible() != visible) { + if (commandGroup) { + commandGroup->addCommand( + new ChangePropertyVisibilityCommand( designerView, set, prop->name(), visible ) ); + } + prop->setVisible( visible ); + changed = true; + } +} + +bool KexiTableDesignerViewPrivate::updatePropertiesVisibility(KexiDB::Field::Type fieldType, KoProperty::Set &set, + CommandGroup *commandGroup) +{ + bool changed = false; + KoProperty::Property *prop; + bool visible; + + prop = &set["subType"]; + kexipluginsdbg << "subType=" << prop->value().toInt() << " type=" << set["type"].value().toInt()<< endl; + + //if there is no more than 1 subType name or it's a PK: hide the property + visible = (prop->listData() && prop->listData()->keys.count() > 1 /*disabled || isObjectTypeGroup*/) + && set["primaryKey"].value().toBool()==false; + setVisibilityIfNeeded( set, prop, visible, changed, commandGroup ); + + prop = &set["objectType"]; + const bool isObjectTypeGroup = set["type"].value().toInt() == (int)KexiDB::Field::BLOB; // used only for BLOBs + visible = isObjectTypeGroup; + setVisibilityIfNeeded( set, prop, visible, changed, commandGroup ); + + prop = &set["unsigned"]; + visible = KexiDB::Field::isNumericType(fieldType); + setVisibilityIfNeeded( set, prop, visible, changed, commandGroup ); + + prop = &set["length"]; + visible = (fieldType == KexiDB::Field::Text); + if (prop->isVisible()!=visible) { +// prop->setVisible( visible ); + //update the length when it makes sense + const int lengthToSet = visible ? KexiDB::Field::defaultTextLength() : 0; + setPropertyValueIfNeeded( set, "length", lengthToSet, + commandGroup, false, false /*!rememberOldValue*/ ); +// if (lengthToSet != prop->value().toInt()) +// prop->setValue( lengthToSet, false ); +// changed = true; + } + setVisibilityIfNeeded( set, prop, visible, changed, commandGroup ); +#ifndef KEXI_NO_UNFINISHED + prop = &set["precision"]; + visible = KexiDB::Field::isFPNumericType(fieldType); + setVisibilityIfNeeded( set, prop, visible, changed, commandGroup ); +#endif + prop = &set["visibleDecimalPlaces"]; + visible = KexiDB::supportsVisibleDecimalPlacesProperty(fieldType); + setVisibilityIfNeeded( set, prop, visible, changed, commandGroup ); + + prop = &set["unique"]; + visible = fieldType != KexiDB::Field::BLOB; + setVisibilityIfNeeded( set, prop, visible, changed, commandGroup ); + + prop = &set["indexed"]; + visible = fieldType != KexiDB::Field::BLOB; + setVisibilityIfNeeded( set, prop, visible, changed, commandGroup ); + + prop = &set["allowEmpty"]; + visible = KexiDB::Field::hasEmptyProperty(fieldType); + setVisibilityIfNeeded( set, prop, visible, changed, commandGroup ); + + prop = &set["autoIncrement"]; + visible = KexiDB::Field::isAutoIncrementAllowed(fieldType); + setVisibilityIfNeeded( set, prop, visible, changed, commandGroup ); + +//! @todo remove this when BLOB supports default value +#ifdef KEXI_NO_UNFINISHED + prop = &set["defaultValue"]; + visible = !isObjectTypeGroup; + setVisibilityIfNeeded( set, prop, visible, changed, commandGroup ); +#endif + + return changed; +} + +QString KexiTableDesignerViewPrivate::messageForSavingChanges(bool &emptyTable, bool skipWarning) +{ + KexiDB::Connection *conn = designerView->mainWin()->project()->dbConnection(); + bool ok; + emptyTable = conn->isEmpty( *designerView->tempData()->table, ok ) && ok; + return i18n("Do you want to save the design now?") + + ( (emptyTable || skipWarning) ? QString::null : + (QString("\n\n") + designerView->part()->i18nMessage(":additional message before saving design", + designerView->parentDialog())) ); +} + +void KexiTableDesignerViewPrivate::updateIconForItem(KexiTableItem &item, KoProperty::Set& set) +{ + QVariant icon; + if (!set["rowSource"].value().toString().isEmpty() && !set["rowSourceType"].value().toString().isEmpty()) + icon = "combo"; + //show/hide icon in the table + view->data()->clearRowEditBuffer(); + view->data()->updateRowEditBuffer(&item, COLUMN_ID_ICON, icon); + view->data()->saveRowChanges(item, true); +} + +#include "kexitabledesignerview_p.moc" diff --git a/kexi/plugins/tables/kexitabledesignerview_p.h b/kexi/plugins/tables/kexitabledesignerview_p.h new file mode 100644 index 00000000..f5650e74 --- /dev/null +++ b/kexi/plugins/tables/kexitabledesignerview_p.h @@ -0,0 +1,191 @@ +/* This file is part of the KDE project + Copyright (C) 2004-2006 Jaroslaw Staniek + + 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 KEXIALTERTABLEDIALOG_P_H +#define KEXIALTERTABLEDIALOG_P_H + +#include "kexitabledesignerview.h" +#include + +class KexiDataAwarePropertySet; + +//! @internal indices for table columns +#define COLUMN_ID_ICON 0 +#define COLUMN_ID_CAPTION 1 +#define COLUMN_ID_TYPE 2 +#define COLUMN_ID_DESC 3 + +/*! @internal + Command group, reimplemented to get access to commands(). + We need it to iterate through commands so we can perform a set of ALTER TABLE atomic actions. */ +class CommandGroup : public KMacroCommand +{ + public: + CommandGroup( const QString & name ) + : KMacroCommand(name) + {} + virtual ~CommandGroup() {} + const QPtrList& commands() const { return m_commands; } +}; + +/*! @internal + Command history, reimplemented to get access to commands(). + We need it to iterate through commands so we can perform a set of ALTER TABLE atomic actions. */ +class CommandHistory : public KCommandHistory +{ + Q_OBJECT + public: + CommandHistory(KActionCollection *actionCollection, bool withMenus = true); + + const QPtrList& commands() const { return m_commandsToUndo; } + + void addCommand(KCommand *command, bool execute = true); + + void clear(); + + public slots: + virtual void undo(); + virtual void redo(); + + protected: + QPtrList m_commandsToUndo, m_commandsToRedo; +}; + +//---------------------------------------------- + +//! @internal +class KexiTableDesignerViewPrivate +{ + public: + KexiTableDesignerViewPrivate(KexiTableDesignerView* aDesignerView); + ~KexiTableDesignerViewPrivate(); + + int generateUniqueId(); + + /*! @internal + Sets property \a propertyName in property set \a set to \a newValue. + If \a commandGroup is not 0, a new ChangeFieldPropertyCommand object is added there as well. + While setting the new value, addHistoryCommand_in_slotPropertyChanged_enabled is set to false, + so addHistoryCommand() wont be executed in slotPropertyChanged() as an answer to setting + the property. + + If \a forceAddCommand is false (the default) and \a newValue does not differ from curent property value + (set[propertyName].value()), ChangeFieldPropertyCommand command is not added to the \a commandGroup. + Otherwise, command is always added. + + \a rememberOldValue argument is passed to Property::setValue() + + If \a slist and \a nlist if not NULL and not empty, these are passed to Property::setListData(). + If \a slist and \a nlist if not NULL but empty, Property::setListData(0) is called. + + addHistoryCommand_in_slotPropertyChanged_enabled is then set back to the original state. + */ + void setPropertyValueIfNeeded( const KoProperty::Set& set, const QCString& propertyName, + const QVariant& newValue, CommandGroup* commandGroup, + bool forceAddCommand = false, bool rememberOldValue = true, + QStringList* const slist = 0, QStringList* const nlist = 0); + + /*! Like above but allows to specify \a oldValue. */ + void setPropertyValueIfNeeded( + const KoProperty::Set& set, const QCString& propertyName, + const QVariant& newValue, const QVariant& oldValue, CommandGroup* commandGroup, + bool forceAddCommand = false, bool rememberOldValue = true, + QStringList* const slist = 0, QStringList* const nlist = 0); + + /*! @internal + Used in updatePropertiesVisibility(). + Does nothing if visibility should not be changed, i.e. when prop->isVisible()==visible, + otherwise sets changed to true and sets visibility of property \a prop to \a visible. + */ + void setVisibilityIfNeeded( const KoProperty::Set& set, KoProperty::Property* prop, + bool visible, bool &changed, CommandGroup *commandGroup ); + + bool updatePropertiesVisibility(KexiDB::Field::Type fieldType, KoProperty::Set &set, + CommandGroup *commandGroup = 0); + + /*! \return message used to ask user for accepting saving the design. + \a emptyTable is set to true if the table designed contains no rows. + If \a skipWarning is true, no warning about data loss is appended (useful when + only non-physical altering actions will be performed). */ + QString messageForSavingChanges(bool &emptyTable, bool skipWarning = false); + + /*! Updates icon in the first column, depending on property set \a set. + For example, when "rowSource" and "rowSourceType" propertiesa are not empty, + "combo" icon appears. */ + void updateIconForItem(KexiTableItem &item, KoProperty::Set& set); + + KexiTableDesignerView* designerView; + + KexiTableView *view; //!< helper + + KexiTableViewData *data; + + KexiDataAwarePropertySet *sets; + + int row; //!< used to know if a new row is selected in slotCellSelected() + + KToggleAction *action_toggle_pkey; + + KPopupTitle *contextMenuTitle; + + int uniqueIdCounter; + + //! internal + int maxTypeNameTextWidth; + //! Set to true in beforeSwitchTo() to avoid asking again in storeData() + bool dontAskOnStoreData : 1; + + bool slotTogglePrimaryKeyCalled : 1; + + bool primaryKeyExists : 1; + //! Used in slotPropertyChanged() to avoid infinite recursion + bool slotPropertyChanged_primaryKey_enabled : 1; + //! Used in slotPropertyChanged() to avoid infinite recursion + bool slotPropertyChanged_subType_enabled : 1; + //! used in slotPropertyChanged() to disable addHistoryCommand() + bool addHistoryCommand_in_slotPropertyChanged_enabled : 1; + //! used in slotRowUpdated() to disable addHistoryCommand() + bool addHistoryCommand_in_slotRowUpdated_enabled : 1; + //! used in slotAboutToDeleteRow() to disable addHistoryCommand() + bool addHistoryCommand_in_slotAboutToDeleteRow_enabled : 1; + //! used in slotRowInserted() to disable addHistoryCommand() + bool addHistoryCommand_in_slotRowInserted_enabled : 1; + + //! used to disable slotBeforeCellChanged() + bool slotBeforeCellChanged_enabled : 1; + +//! @tood temp; remove this: + //! Temporary flag, used for testingu the Alter Table machinery. Affects storeData() + //! Used in slotExecuteRealAlterTable() to switch on real alter table for a while. + bool tempStoreDataUsingRealAlterTable : 1; + + /*! Set to a recent result of calling \ref tristate KexiTableDesignerView::storeData(bool dontAsk). + Then, it is used in \ref void KexiTableDesignerView::executeRealAlterTable() + to know what return value should be. */ + tristate recentResultOfStoreData; + + KActionCollection* historyActionCollection; + CommandHistory* history; + + //! A cache used in KexiTableDesignerView::buildField() to quickly identify + //! properties internal to the designer + QAsciiDict internalPropertyNames; +}; + +#endif diff --git a/kexi/plugins/tables/kexitablehandler.desktop b/kexi/plugins/tables/kexitablehandler.desktop new file mode 100644 index 00000000..8491b7a3 --- /dev/null +++ b/kexi/plugins/tables/kexitablehandler.desktop @@ -0,0 +1,118 @@ +[Desktop Entry] +Type=Service +ServiceTypes=Kexi/Handler + +GenericName=Tables +GenericName[bg]=Таблици +GenericName[br]=Taolennoù +GenericName[ca]=Taules +GenericName[cs]=Tabulky +GenericName[cy]=Tablau +GenericName[da]=Tabeller +GenericName[de]=Tabellen +GenericName[el]=Πίνακες +GenericName[eo]=Tabeloj +GenericName[es]=Tablas +GenericName[et]=Tabelid +GenericName[eu]=Taulak +GenericName[fa]=جدولها +GenericName[fi]=Taulukot +GenericName[fr]=Tableaux +GenericName[fy]=Tabellen +GenericName[ga]=Táblaí +GenericName[gl]=Táboas +GenericName[he]=טבלאות +GenericName[hi]=तालिका +GenericName[hr]=Tablice +GenericName[hu]=Táblák +GenericName[is]=Töflur +GenericName[it]=Tabelle +GenericName[ja]=テーブル +GenericName[km]=តារាង​ +GenericName[lt]=Lentelės +GenericName[lv]=Tabulas +GenericName[ms]=Jadual +GenericName[nb]=Tabeller +GenericName[nds]=Tabellen +GenericName[ne]=तालिका +GenericName[nl]=Tabellen +GenericName[nn]=Tabellar +GenericName[pl]=Tabele +GenericName[pt]=Tabelas +GenericName[pt_BR]=Tabelas +GenericName[ru]=Таблицы +GenericName[se]=Tabeallat +GenericName[sk]=Tabuľky +GenericName[sl]=Tabele +GenericName[sr]=Табеле +GenericName[sr@Latn]=Tabele +GenericName[sv]=Tabeller +GenericName[ta]=அட்டவணைகள் +GenericName[tr]=Tablolar +GenericName[uk]=Таблиці +GenericName[uz]=Jadvallar +GenericName[uz@cyrillic]=Жадваллар +GenericName[zh_CN]=表 +GenericName[zh_TW]=表格 +Name=Tables +Name[bg]=Таблици +Name[br]=Taolennoù +Name[ca]=Taules +Name[cs]=Tabulky +Name[cy]=Tablau +Name[da]=Tabeller +Name[de]=Tabellen +Name[el]=Πίνακες +Name[eo]=Tabeloj +Name[es]=Tablas +Name[et]=Tabelid +Name[eu]=Taulak +Name[fa]=جدولها +Name[fi]=Taulukot +Name[fr]=Tableaux +Name[fy]=Tabellen +Name[ga]=Táblaí +Name[gl]=Táboas +Name[he]=טבלאות +Name[hi]=टेबल्स +Name[hr]=Tablice +Name[hu]=Táblák +Name[is]=Töflur +Name[it]=Tabelle +Name[ja]=テーブル +Name[km]=តារាង​ +Name[lt]=Lentelės +Name[lv]=Tabulas +Name[ms]=Jadual +Name[nb]=Tabeller +Name[nds]=Tabellen +Name[ne]=तालिकाहरू +Name[nl]=Tabellen +Name[nn]=Tabellar +Name[pl]=Tabele +Name[pt]=Tabelas +Name[pt_BR]=Tabelas +Name[ru]=Таблицы +Name[se]=Tabeallat +Name[sk]=Tabuľky +Name[sl]=Tabele +Name[sr]=Табеле +Name[sr@Latn]=Tabele +Name[sv]=Tabeller +Name[ta]=அட்டவணை +Name[tg]=Ҷадвалҳо +Name[tr]=Tablolar +Name[uk]=Таблиці +Name[uz]=Jadvallar +Name[uz@cyrillic]=Жадваллар +Name[wa]=Tåvleas +Name[zh_CN]=表 +Name[zh_TW]=表格 +X-KDE-Library=kexihandler_table +X-KDE-ParentApp=kexi +X-Kexi-PartVersion=2 +X-Kexi-TypeName=table +X-Kexi-TypeMime=kexi/table +X-Kexi-ItemIcon=table +X-Kexi-SupportsDataExport=true +X-Kexi-SupportsPrinting=true diff --git a/kexi/plugins/tables/kexitablepart.cpp b/kexi/plugins/tables/kexitablepart.cpp new file mode 100644 index 00000000..3d09a81e --- /dev/null +++ b/kexi/plugins/tables/kexitablepart.cpp @@ -0,0 +1,313 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Lucijan Busch + Copyright (C) 2002, 2003 Joseph Wenninger + Copyright (C) 2004 Jaroslaw Staniek + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "kexitablepart.h" + +#include +#include +#include +#include +#include + +#include "keximainwindow.h" +#include "kexiproject.h" +#include "kexipartinfo.h" +#include "widget/kexidatatable.h" +#include "widget/tableview/kexidatatableview.h" +#include "kexitabledesignerview.h" +#include "kexitabledesigner_dataview.h" +#include "kexilookupcolumnpage.h" + +#include +#include +#include + +//! @internal +class KexiTablePart::Private +{ + public: + Private() + { + } + ~Private() + { + delete static_cast(lookupColumnPage); + } + QGuardedPtr lookupColumnPage; +}; + +KexiTablePart::KexiTablePart(QObject *parent, const char *name, const QStringList &l) + : KexiPart::Part(parent, name, l) + , d(new Private()) +{ + // REGISTERED ID: + m_registeredPartID = (int)KexiPart::TableObjectType; + + kdDebug() << "KexiTablePart::KexiTablePart()" << endl; + m_names["instanceName"] + = i18n("Translate this word using only lowercase alphanumeric characters (a..z, 0..9). " + "Use '_' character instead of spaces. First character should be a..z character. " + "If you cannot use latin characters in your language, use english word.", + "table"); + m_names["instanceCaption"] = i18n("Table"); + m_supportedViewModes = Kexi::DataViewMode | Kexi::DesignViewMode; +//js TODO: also add Kexi::TextViewMode when we'll have SQL ALTER TABLE EDITOR!!! +} + +KexiTablePart::~KexiTablePart() +{ + delete d; +} + +void KexiTablePart::initPartActions() +{ +} + +void KexiTablePart::initInstanceActions() +{ +//moved to main window createSharedAction(Kexi::DataViewMode, i18n("Filter"), "filter", 0, "tablepart_filter"); + + KAction *a = createSharedToggleAction( + Kexi::DesignViewMode, i18n("Primary Key"), "key", 0, "tablepart_toggle_pkey"); +// Kexi::DesignViewMode, i18n("Toggle Primary Key"), "key", 0, "tablepart_toggle_pkey"); + a->setWhatsThis(i18n("Sets or removes primary key for currently selected field.")); +} + +KexiDialogTempData* KexiTablePart::createTempData(KexiDialogBase* dialog) +{ + return new KexiTablePart::TempData(dialog); +} + +KexiViewBase* KexiTablePart::createView(QWidget *parent, KexiDialogBase* dialog, + KexiPart::Item &item, int viewMode, QMap*) +{ + KexiMainWindow *win = dialog->mainWin(); + if (!win || !win->project() || !win->project()->dbConnection()) + return 0; + + + KexiTablePart::TempData *temp = static_cast(dialog->tempData()); + if (!temp->table) { + temp->table = win->project()->dbConnection()->tableSchema(item.name()); + kdDebug() << "KexiTablePart::execute(): schema is " << temp->table << endl; + } + + if (viewMode == Kexi::DesignViewMode) { + KexiTableDesignerView *t = new KexiTableDesignerView(win, parent); + return t; + } + else if (viewMode == Kexi::DataViewMode) { + if(!temp->table) + return 0; //todo: message + //we're not setting table schema here -it will be forced to set + // in KexiTableDesigner_DataView::afterSwitchFrom() + KexiTableDesigner_DataView *t = new KexiTableDesigner_DataView(win, parent); + return t; + } + return 0; +} + +bool KexiTablePart::remove(KexiMainWindow *win, KexiPart::Item &item) +{ + if (!win || !win->project() || !win->project()->dbConnection()) + return false; + + KexiDB::Connection *conn = win->project()->dbConnection(); + KexiDB::TableSchema *sch = conn->tableSchema(item.identifier()); + + if (sch) { + tristate res = KexiTablePart::askForClosingObjectsUsingTableSchema( + win, *conn, *sch, + i18n("You are about to remove table \"%1\" but following objects using this table are opened:") + .arg(sch->name())); + return true == conn->dropTable( sch ); + } + //last chance: just remove item + return conn->removeObject( item.identifier() ); +} + +tristate KexiTablePart::rename(KexiMainWindow *win, KexiPart::Item & item, + const QString& newName) +{ +//TODO: what about objects (queries/forms) that use old name? + KexiDB::Connection *conn = win->project()->dbConnection(); + KexiDB::TableSchema *sch = conn->tableSchema(item.identifier()); + if (!sch) + return false; + return conn->alterTableName(*sch, newName); +} + +KexiDB::SchemaData* +KexiTablePart::loadSchemaData(KexiDialogBase *dlg, const KexiDB::SchemaData& sdata, int viewMode) +{ + Q_UNUSED( viewMode ); + + return dlg->mainWin()->project()->dbConnection()->tableSchema( sdata.name() ); +} + +#if 0 +KexiPart::DataSource * +KexiTablePart::dataSource() +{ + return new KexiTableDataSource(this); +} +#endif + +tristate KexiTablePart::askForClosingObjectsUsingTableSchema(QWidget *parent, KexiDB::Connection& conn, + KexiDB::TableSchema& table, const QString& msg) +{ + QPtrList* listeners + = conn.tableSchemaChangeListeners(table); + if (!listeners || listeners->isEmpty()) + return true; + + QString openedObjectsStr = "
    "; + for (QPtrListIterator it(*listeners); + it.current(); ++it) { + openedObjectsStr += QString("
  • %1
  • ").arg(it.current()->listenerInfoString); + } + openedObjectsStr += "
"; + int r = KMessageBox::questionYesNo(parent, + "

"+msg+"

"+openedObjectsStr+"

" + +i18n("Do you want to close all windows for these objects?"), + QString::null, KGuiItem(i18n("Close windows"),"fileclose"), KStdGuiItem::cancel()); + tristate res; + if (r == KMessageBox::Yes) { + //try to close every window + res = conn.closeAllTableSchemaChangeListeners(table); + if (res!=true) //do not expose closing errors twice; just cancel + res = cancelled; + } + else + res = cancelled; + + return res; +} + +QString +KexiTablePart::i18nMessage(const QCString& englishMessage, KexiDialogBase* dlg) const +{ + if (englishMessage=="Design of object \"%1\" has been modified.") + return i18n("Design of table \"%1\" has been modified."); + + if (englishMessage=="Object \"%1\" already exists.") + return i18n("Table \"%1\" already exists."); + + if (dlg->currentViewMode()==Kexi::DesignViewMode && !dlg->neverSaved() + && englishMessage==":additional message before saving design") + return i18n("Warning! Any data in this table will be removed upon design's saving!"); + + return englishMessage; +} + +void KexiTablePart::setupCustomPropertyPanelTabs(KTabWidget *tab, KexiMainWindow* mainWin) +{ + if (!d->lookupColumnPage) { + d->lookupColumnPage = new KexiLookupColumnPage(0); + connect(d->lookupColumnPage, SIGNAL(jumpToObjectRequested(const QCString&, const QCString&)), + mainWin, SLOT(highlightObject(const QCString&, const QCString&))); + +//! @todo add "Table" tab + + /* + connect(d->dataSourcePage, SIGNAL(formDataSourceChanged(const QCString&, const QCString&)), + KFormDesigner::FormManager::self(), SLOT(setFormDataSource(const QCString&, const QCString&))); + connect(d->dataSourcePage, SIGNAL(dataSourceFieldOrExpressionChanged(const QString&, const QString&, KexiDB::Field::Type)), + KFormDesigner::FormManager::self(), SLOT(setDataSourceFieldOrExpression(const QString&, const QString&, KexiDB::Field::Type))); + connect(d->dataSourcePage, SIGNAL(insertAutoFields(const QString&, const QString&, const QStringList&)), + KFormDesigner::FormManager::self(), SLOT(insertAutoFields(const QString&, const QString&, const QStringList&)));*/ + } + + KexiProject *prj = mainWin->project(); + d->lookupColumnPage->setProject(prj); + +//! @todo add lookup field icon + tab->addTab( d->lookupColumnPage, SmallIconSet("combo"), ""); + tab->setTabToolTip( d->lookupColumnPage, i18n("Lookup column")); +} + +KexiLookupColumnPage* KexiTablePart::lookupColumnPage() const +{ + return d->lookupColumnPage; +} + +//---------------- + +#if 0 +KexiTableDataSource::KexiTableDataSource(KexiPart::Part *part) + : KexiPart::DataSource(part) +{ +} + +KexiTableDataSource::~KexiTableDataSource() +{ +} + +KexiDB::FieldList * +KexiTableDataSource::fields(KexiProject *project, const KexiPart::Item &it) +{ + kdDebug() << "KexiTableDataSource::fields(): " << it.name() << endl; + return project->dbConnection()->tableSchema(it.name()); +} + +KexiDB::Cursor * +KexiTableDataSource::cursor(KexiProject * /*project*/, + const KexiPart::Item &/*it*/, bool /*buffer*/) +{ + return 0; +} +#endif + +//---------------- + +KexiTablePart::TempData::TempData(QObject* parent) + : KexiDialogTempData(parent) + , table(0) + , tableSchemaChangedInPreviousView(true /*to force reloading on startup*/ ) +{ +} + +//---------------- + +/** +TODO +*/ +/* +AboutData( const char *programName, + const char *version, + const char *i18nShortDescription = 0, + int licenseType = License_Unknown, + const char *i18nCopyrightStatement = 0, + const char *i18nText = 0, + const char *homePageAddress = 0, + const char *bugsEmailAddress = "submit@bugs.kde.org" +); + +#define KEXIPART_EXPORT_FACTORY( libname, partClass, aboutData ) \ + static KexiPart::AboutData * libname ## updateAD(KexiPart::AboutData *ad) \ + { ad->setAppName( #libname ); return ad; } \ + K_EXPORT_COMPONENT_FACTORY( libname, KGenericFactory(libname ## updateAD(#libname)) ) +*/ + +K_EXPORT_COMPONENT_FACTORY( kexihandler_table, KGenericFactory("kexihandler_table") ) + +#include "kexitablepart.moc" + diff --git a/kexi/plugins/tables/kexitablepart.h b/kexi/plugins/tables/kexitablepart.h new file mode 100644 index 00000000..e4b060ad --- /dev/null +++ b/kexi/plugins/tables/kexitablepart.h @@ -0,0 +1,100 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Lucijan Busch + Copyright (C) 2002, 2003 Joseph Wenninger + Copyright (C) 2004 Jaroslaw Staniek + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXITABLEPART_H +#define KEXITABLEPART_H + +#include +#include +#include +//#include +#include +#include + +class KexiMainWin; +class KexiLookupColumnPage; + +class KexiTablePart : public KexiPart::Part +{ + Q_OBJECT + + public: + KexiTablePart(QObject *parent, const char *name, const QStringList &); + virtual ~KexiTablePart(); + + virtual bool remove(KexiMainWindow *win, KexiPart::Item &item); + + virtual tristate rename(KexiMainWindow *win, KexiPart::Item &item, + const QString& newName); + +// virtual KexiPart::DataSource *dataSource(); + + class TempData : public KexiDialogTempData + { + public: + TempData(QObject* parent); + KexiDB::TableSchema *table; + /*! true, if \a table member has changed in previous view. Used on view switching. + We're checking this flag to see if we should refresh data for DataViewMode. */ + bool tableSchemaChangedInPreviousView : 1; + }; + + static tristate askForClosingObjectsUsingTableSchema( + QWidget *parent, KexiDB::Connection& conn, + KexiDB::TableSchema& table, const QString& msg); + + virtual QString i18nMessage(const QCString& englishMessage, + KexiDialogBase* dlg) const; + + KexiLookupColumnPage* lookupColumnPage() const; + + protected: + virtual KexiDialogTempData* createTempData(KexiDialogBase* dialog); + + virtual KexiViewBase* createView(QWidget *parent, KexiDialogBase* dialog, + KexiPart::Item &item, int viewMode = Kexi::DataViewMode, QMap* staticObjectArgs = 0); + + virtual void initPartActions(); + virtual void initInstanceActions(); + + virtual void setupCustomPropertyPanelTabs(KTabWidget *tab, KexiMainWindow* mainWin); + + virtual KexiDB::SchemaData* loadSchemaData(KexiDialogBase *dlg, const KexiDB::SchemaData& sdata, int viewMode); + + private: + class Private; + Private* d; +}; + +#if 0 +class KexiTableDataSource : public KexiPart::DataSource +{ + public: + KexiTableDataSource(KexiPart::Part *part); + ~KexiTableDataSource(); + + virtual KexiDB::FieldList *fields(KexiProject *project, const KexiPart::Item &item); + virtual KexiDB::Cursor *cursor(KexiProject *project, const KexiPart::Item &item, bool buffer); +}; +#endif + +#endif + diff --git a/kexi/plugins/tables/kexitablepartinstui.rc b/kexi/plugins/tables/kexitablepartinstui.rc new file mode 100644 index 00000000..e96a5976 --- /dev/null +++ b/kexi/plugins/tables/kexitablepartinstui.rc @@ -0,0 +1,18 @@ + + + + +

+ &Edit + + + + + + + Design + + + + + diff --git a/kexi/plugins/tables/kexitablepartui.rc b/kexi/plugins/tables/kexitablepartui.rc new file mode 100644 index 00000000..c78b2587 --- /dev/null +++ b/kexi/plugins/tables/kexitablepartui.rc @@ -0,0 +1,7 @@ + + + + + + + -- cgit v1.2.1