/* 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 tryCastTQVariant() anf canCastTQVariant() static bool isIntegerTQVariant(TQVariant::Type t) { return t==TQVariant::LongLong || t==TQVariant::ULongLong || t==TQVariant::Int || t==TQVariant::UInt; } //! @internal Used in tryCastTQVariant() static bool canCastTQVariant(TQVariant::Type fromType, TQVariant::Type toType) { return (fromType==TQVariant::Int && toType==TQVariant::UInt) || (fromType==TQVariant::CString && toType==TQVariant::String) || (fromType==TQVariant::LongLong && toType==TQVariant::ULongLong) || ((fromType==TQVariant::String || fromType==TQVariant::CString) && (isIntegerTQVariant(toType) || toType==TQVariant::Double)); } /*! @internal \return a variant value converted from \a fromVal to \a toType type. Null TQVariant is returned if \a fromVal's type and \a toType type are incompatible. */ static TQVariant tryCastTQVariant( const TQVariant& fromVal, TQVariant::Type toType ) { const TQVariant::Type fromType = fromVal.type(); if (fromType == toType) return fromVal; if (canCastTQVariant(fromType, toType) || canCastTQVariant(toType, fromType) || (isIntegerTQVariant(fromType) && toType==TQVariant::Double)) { TQVariant res( fromVal ); if (res.cast(toType)) return res; } return TQVariant(); } KexiTableDesignerView::KexiTableDesignerView(KexiMainWindow *win, TQWidget *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, TQString(), 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 TQValueVector types(KexiDB::Field::LastTypeGroup-1); //don't show last type (BLOB) #else TQValueVector types(KexiDB::Field::LastTypeGroup); #endif d->maxTypeNameTextWidth = 0; TQFontMetrics fm(font()); for (uint i=1; i<=types.count(); i++) { types[i-1] = KexiDB::Field::typeGroupName(i); d->maxTypeNameTextWidth = TQMAX(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, TQT_SIGNAL(aboutToChangeCell(KexiTableItem*,int,TQVariant&,KexiDB::ResultInfo*)), TQT_TQOBJECT(this), TQT_SLOT(slotBeforeCellChanged(KexiTableItem*,int,TQVariant&,KexiDB::ResultInfo*))); connect(d->data, TQT_SIGNAL(rowUpdated(KexiTableItem*)), TQT_TQOBJECT(this), TQT_SLOT(slotRowUpdated(KexiTableItem*))); //connect(d->data, TQT_SIGNAL(aboutToInsertRow(KexiTableItem*,KexiDB::ResultInfo*,bool)), // TQT_TQOBJECT(this), TQT_SLOT(slotAboutToInsertRow(KexiTableItem*,KexiDB::ResultInfo*,bool))); connect(d->data, TQT_SIGNAL(aboutToDeleteRow(KexiTableItem&,KexiDB::ResultInfo*,bool)), TQT_TQOBJECT(this), TQT_SLOT(slotAboutToDeleteRow(KexiTableItem&,KexiDB::ResultInfo*,bool))); setMinimumSize(d->view->tqminimumSizeHint().width(), d->view->tqminimumSizeHint().height()); d->view->setFocus(); d->sets = new KexiDataAwarePropertySet( this, d->view ); connect(d->sets, TQT_SIGNAL(rowDeleted()), TQT_TQOBJECT(this), TQT_SLOT(updateActions())); connect(d->sets, TQT_SIGNAL(rowInserted()), TQT_TQOBJECT(this), TQT_SLOT(slotRowInserted())); d->contextMenuTitle = new KPopupTitle(d->view->contextMenu()); d->view->contextMenu()->insertItem(d->contextMenuTitle, -1, 0); connect(d->view->contextMenu(), TQT_SIGNAL(aboutToShow()), TQT_TQOBJECT(this), TQT_SLOT(slotAboutToShowContextMenu())); plugSharedAction("tablepart_toggle_pkey", TQT_TQOBJECT(this), TQT_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", TQT_TQOBJECT(this), TQT_SLOT(slotUndo())); plugSharedAction("edit_redo", TQT_TQOBJECT(this), TQT_SLOT(slotRedo())); setAvailable("edit_undo", false); setAvailable("edit_redo", false); connect(d->history, TQT_SIGNAL(commandExecuted(KCommand*)), TQT_TQOBJECT(this), TQT_SLOT(slotCommandExecuted(KCommand*))); #endif #ifdef KEXI_DEBUG_GUI KexiUtils::addAlterTableActionDebug(TQString()); //to create the tab KexiUtils::connectPushButtonActionForDebugWindow( "simulateAlterTableExecution", TQT_TQOBJECT(this), TQT_SLOT(slotSimulateAlterTableExecution())); KexiUtils::connectPushButtonActionForDebugWindow( "executeRealAlterTable", TQT_TQOBJECT(this), TQT_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, TQStringList& stringsList, TQStringList& 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 ) { TQString typeName = "KexiDB::Field::" + field.typeGroupString(); KoProperty::Set *set = new KoProperty::Set(d->sets, typeName); if (mainWin()->project()->dbConnection()->isReadOnly()) set->setReadOnly( true ); // connect(buff,TQT_SIGNAL(propertyChanged(KexiPropertyBuffer&,KexiProperty&)), // TQT_TQOBJECT(this), TQT_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", TQVariant(true, 1), TQString())); //we want "caption" to be displayed in the header, not name prop->setVisible(false); //name set->addProperty(prop = new KoProperty::Property("name", TQVariant(field.name()), i18n("Name"), TQString(), KexiCustomPropertyFactory::Identifier) ); //type set->addProperty( prop = new KoProperty::Property("type", TQVariant(field.type()), i18n("Type")) ); #ifndef KexiTableDesignerView_DEBUG prop->setVisible(false);//always hidden #endif //subtype TQStringList typeStringList, typeNameList; getSubTypeListData(field.typeGroup(), typeStringList, typeNameList); /* disabled - "mime" is moved from subType to "objectType" custom property TQString 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 {*/ TQString subTypeValue = field.typeString(); //} set->addProperty(prop = new KoProperty::Property("subType", typeStringList, typeNameList, subTypeValue, i18n("Subtype"))); // objectType TQStringList objectTypeStringList, objectTypeNameList; //! @todo this should be retrieved from KexiDB::Field when BLOB supports many different mimetypes objectTypeStringList << "image"; objectTypeNameList << i18n("Image object type", "Image"); TQString 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", TQVariant(field.caption()), i18n("Caption") ) ); prop->setVisible(false);//always hidden set->addProperty( prop = new KoProperty::Property("description", TQVariant(field.description())) ); prop->setVisible(false);//always hidden set->addProperty(prop = new KoProperty::Property("unsigned", TQVariant(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"), TQString(), //! @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", TQVariant(field.isPrimaryKey(), 4), i18n("Primary Key"))); prop->setIcon("key"); set->addProperty( prop = new KoProperty::Property("unique", TQVariant(field.isUniqueKey(), 4), i18n("Unique"))); set->addProperty( prop = new KoProperty::Property("notNull", TQVariant(field.isNotNull(), 4), i18n("Required"))); set->addProperty( prop = new KoProperty::Property("allowEmpty", TQVariant(!field.isNotEmpty(), 4), i18n("Allow Zero\nSize"))); set->addProperty( prop = new KoProperty::Property("autoIncrement", TQVariant(field.isAutoIncrement(), 4), i18n("Autonumber"))); prop->setIcon("autonumber"); set->addProperty( prop = new KoProperty::Property("indexed", TQVariant(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() : TQString(), i18n("Row Source"))); prop->setVisible(false); set->addProperty( prop = new KoProperty::Property("rowSourceType", lookupFieldSchema ? lookupFieldSchema->rowSource().typeName() : TQString(), 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, TQT_SIGNAL(propertyChanged(KoProperty::Set&, KoProperty::Property&)), TQT_TQOBJECT(this), TQT_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", TQVariant(isSet,1)); //this will update all related properties as well /* CommandGroup *setPrimaryKeyCommand; if (isSet) { setPrimaryKeyCommand = new CommandGroup(i18n("Set primary key for field \"%1\"") .tqarg(set["name"].value().toString()) ); } else { setPrimaryKeyCommand = new CommandGroup(i18n("Unset primary key for field \"%1\"") .tqarg(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"] = TQVariant(set, 1); d->setPropertyValueIfNeeded( propertySet, "primaryKey", TQVariant(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->KexiDataAwareObjectInterface::data()->clearRowEditBuffer(); d->view->KexiDataAwareObjectInterface::data()->updateRowEditBuffer(d->view->selectedItem(), COLUMN_ID_ICON, TQVariant(set ? "key" : "")); d->view->KexiDataAwareObjectInterface::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", TQVariant(false,0), commandGroup ); //(*s)["primaryKey"] = TQVariant(false, 0); d->setPropertyValueIfNeeded( *s, "primaryKey", TQVariant(false,0), commandGroup ); //remove key from table d->view->KexiDataAwareObjectInterface::data()->clearRowEditBuffer(); KexiTableItem *item = d->view->itemAt(i); if (item) { d->view->KexiDataAwareObjectInterface::data()->updateRowEditBuffer(item, COLUMN_ID_ICON, TQVariant()); d->view->KexiDataAwareObjectInterface::data()->saveRowChanges(*item, true); } } //set unsigned big-integer type // d->view->KexiDataAwareObjectInterface::data()->saveRowChanges(*d->view->selectedItem()); d->slotBeforeCellChanged_enabled = false; d->view->KexiDataAwareObjectInterface::data()->clearRowEditBuffer(); d->view->KexiDataAwareObjectInterface::data()->updateRowEditBuffer(d->view->selectedItem(), COLUMN_ID_TYPE, TQVariant(KexiDB::Field::IntegerGroup-1/*counting from 0*/)); // TQVariant(KexiDB::Field::typeGroupName(KexiDB::Field::IntegerGroup))); d->view->KexiDataAwareObjectInterface::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"] = TQVariant(true,4); d->setPropertyValueIfNeeded( propertySet, "unsigned", TQVariant(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()), TQString(), KStdGuiItem::save(), KStdGuiItem::discard(), TQString(), 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, TQVariant& 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->KexiDataAwareObjectInterface::data()->updateRowEditBuffer(item, COLUMN_ID_TYPE, TQVariant((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 TQString oldName( propertySetForItem->property("name").value().toString() ); TQString 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\"") .tqarg(oldName).tqarg(propertySetForItem->property("name").value().toString()) .tqarg(oldCaption).tqarg(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->KexiDataAwareObjectInterface::data()->updateRowEditBuffer(item, COLUMN_ID_ICON, TQVariant()); d->view->KexiDataAwareObjectInterface::data()->updateRowEditBuffer(item, COLUMN_ID_CAPTION, TQVariant(TQString())); d->view->KexiDataAwareObjectInterface::data()->updateRowEditBuffer(item, COLUMN_ID_DESC, TQVariant()); 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) TQStringList slist, nlist; getSubTypeListData(fieldTypeGroup, slist, nlist); TQString 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\"") .tqarg(set["name"].value().toString()).tqarg( 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", TQVariant(true, 1), changeDataTypeCommand, false /*!forceAddCommand*/, false /*!rememberOldValue*/); d->setPropertyValueIfNeeded( set, "defaultValue", TQVariant(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->KexiDataAwareObjectInterface::data()->updateRowEditBuffer(item, COLUMN_ID_ICON, TQVariant()); d->view->KexiDataAwareObjectInterface::data()->saveRowChanges(*item); */ //set["primaryKey"] = TQVariant(false, 1); d->setPropertyValueIfNeeded( set, "primaryKey", TQVariant(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. TQVariant 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->KexiDataAwareObjectInterface::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) TQString 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->KexiDataAwareObjectInterface::data()->clearRowEditBuffer(); // d->view->KexiDataAwareObjectInterface::data()->updateRowEditBuffer(d->view->selectedItem(), COLUMN_ID_TYPE, TQVariant()); d->view->KexiDataAwareObjectInterface::data()->updateRowEditBuffer(item, COLUMN_ID_TYPE, TQVariant()); d->view->KexiDataAwareObjectInterface::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; TQString description( item->at(COLUMN_ID_DESC).toString() ); //todo: check uniqueness: TQString 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*/TQVariant(), 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( TQVariant(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", TQVariant()); // 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 TQCString 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! TQString msg = TQString("

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

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

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

"); msg += (TQString("

") +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\"").tqarg(set["name"].value().toString()) ); toplevelCommand = setAutonumberCommand; d->setPropertyValueIfNeeded( set, "autoIncrement", TQVariant(true,1), setAutonumberCommand ); } else { setAutonumberCommand = new CommandGroup( i18n("Remove autonumber from field \"%1\"").tqarg(set["name"].value().toString()) ); //d->slotPropertyChanged_enabled = false; // set["autoIncrement"].setValue( TQVariant(false,1), false/*don't save old*/); // d->slotPropertyChanged_enabled = true; d->setPropertyValueIfNeeded( set, "autoIncrement", TQVariant(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\"").tqarg(property.caption()).tqarg(set["name"].value().toString()) ); toplevelCommand = unsetIndexedOrUniquOrNotNullCommand; d->setPropertyValueIfNeeded( set, pname, TQVariant(false,1), unsetIndexedOrUniquOrNotNullCommand ); if (pname=="notNull") { //? d->setPropertyValueIfNeeded( set, "notNull", TQVariant(true,1), unsetIndexedOrUniquOrNotNullCommand ); d->setPropertyValueIfNeeded( set, "unique", TQVariant(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() ); TQString typeName; /* disabled - "mime" is moved from subType to "objectType" custom property if (type==KexiDB::Field::BLOB) { //special case //find i18n'd text TQStringList 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\"").tqarg(set["name"].value().toString()) .tqarg(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 TQVariant oldDefVal( set["defaultValue"].value() ); TQVariant newDefVal( tryCastTQVariant(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\"") .tqarg(set["name"].value().toString()) ); if (toplevelCommand) toplevelCommand->addCommand( setPrimaryKeyCommand ); else toplevelCommand = setPrimaryKeyCommand; d->setPropertyValueIfNeeded( set, "primaryKey", TQVariant(true,1), setPrimaryKeyCommand, true /*forceAddCommand*/ ); d->setPropertyValueIfNeeded( set, "unique", TQVariant(true,1), setPrimaryKeyCommand ); d->setPropertyValueIfNeeded( set, "notNull", TQVariant(true,1), setPrimaryKeyCommand ); d->setPropertyValueIfNeeded( set, "allowEmpty", TQVariant(false,1), setPrimaryKeyCommand ); d->setPropertyValueIfNeeded( set, "indexed", TQVariant(true,1), setPrimaryKeyCommand ); //! \todo: add setting for this: "Integer PKeys have autonumber set by default" d->setPropertyValueIfNeeded( set, "autoIncrement", TQVariant(true,1), setPrimaryKeyCommand ); /* set["unique"] = TQVariant(true,1); set["notNull"] = TQVariant(true,1); set["allowEmpty"] = TQVariant(false,1); set["indexed"] = TQVariant(true,1); set["autoIncrement"] = TQVariant(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\"") .tqarg(set["name"].value().toString()) ); if (toplevelCommand) toplevelCommand->addCommand( setPrimaryKeyCommand ); else toplevelCommand = setPrimaryKeyCommand; d->setPropertyValueIfNeeded( set, "primaryKey", TQVariant(false,1), setPrimaryKeyCommand, true /*forceAddCommand*/ ); d->setPropertyValueIfNeeded( set, "autoIncrement", TQVariant(false,1), setPrimaryKeyCommand ); // set["autoIncrement"] = TQVariant(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 tqrepaint) { Q_UNUSED(result) Q_UNUSED(tqrepaint) if (item[COLUMN_ID_ICON].toString()=="key") d->primaryKeyExists = false; if (d->addHistoryCommand_in_slotAboutToDeleteRow_enabled) { const int row = d->view->KexiDataAwareObjectInterface::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; TQMap values = KoProperty::propertyValues(set); //remove internal values, to avoid creating custom field's properties TQMap::Iterator it = values.begin(); KexiDB::Field *field = new KexiDB::Field(); while (it!=values.end()) { const TQString propName( it.key() ); if (d->internalPropertyNames.find(propName.latin1()) || propName.startsWith("this:") || (/*sanity*/propName=="objectType" && KexiDB::Field::BLOB != KexiDB::intToFieldType( set["type"].value().toInt() ))) { TQMap::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.

").tqarg(schema.name()), TQString(), 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" TQString pkFieldName("id%1"); TQString 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.tqarg(idIndex==1?TQString() : TQString::number(idIndex)) || (*set)["caption"].value().toString() == pkFieldCaption.tqarg(idIndex==1?TQString() : TQString::number(idIndex))) { //try next id index i = 0; idIndex++; continue; } } i++; } pkFieldName = pkFieldName.tqarg(idIndex==1?TQString() : TQString::number(idIndex)); pkFieldCaption = pkFieldCaption.tqarg(idIndex==1?TQString() : TQString::number(idIndex)); //ok, add PK with such unique name d->view->insertEmptyRow(0); d->view->setCursorPosition(0, COLUMN_ID_CAPTION); d->view->KexiDataAwareObjectInterface::data()->updateRowEditBuffer(d->view->selectedItem(), COLUMN_ID_CAPTION, TQVariant(pkFieldCaption)); d->view->KexiDataAwareObjectInterface::data()->updateRowEditBuffer(d->view->selectedItem(), COLUMN_ID_TYPE, TQVariant(KexiDB::Field::IntegerGroup-1/*counting from 0*/)); if (!d->view->KexiDataAwareObjectInterface::data()->saveRowChanges(*d->view->selectedItem(), true)) { return cancelled; } slotTogglePrimaryKey(); } } } //check for duplicates KoProperty::Set *b = 0; bool no_fields = true; int i; TQDict 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 TQString name = (*b)["name"].value().toString(); if (name.isEmpty()) { if (beSilent) { kexipluginswarn << TQString("KexiTableDesignerView::buildSchema(): no field caption entered at row %1...") .tqarg(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 << TQString("KexiTableDesignerView::buildSchema(): duplicated field name '%1'") .tqarg((*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.") .tqarg((*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 TQValueList 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 (TQPtrListIterator 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 (TQPtrListIterator 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()->settqStatus(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:") .tqarg(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 TQString 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()->settqStatus(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()->settqStatus(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(TQString *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() { TQSignal signal; signal.connect( mainWin(), TQT_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(); TQVariant old_type = item->at(1); TQVariant *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 (TQPtrListIterator 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(TQString("UNDO:")); # endif d->history->undo(); updateUndoRedoActions(); #endif } void KexiTableDesignerView::slotRedo() { #ifndef KEXI_NO_UNDOREDO_ALTERTABLE # ifdef KEXI_DEBUG_GUI KexiUtils::addAlterTableActionDebug(TQString("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(); TQString captionOrName(set["caption"].value().toString()); if (captionOrName.isEmpty()) captionOrName = set["name"].value().toString(); //! @todo show "field" icon d->contextMenuTitle->setTitle( i18n("Table field \"%1\"").tqarg(captionOrName) ); } else { d->contextMenuTitle->setTitle( i18n("Empty table row", "Empty Row") ); } } TQString 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 TQString(); 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->KexiDataAwareObjectInterface::data()->columnsCount(); i++) { if (!addCommand) { d->addHistoryCommand_in_slotRowUpdated_enabled = false; d->addHistoryCommand_in_slotPropertyChanged_enabled = false; d->slotBeforeCellChanged_enabled = false; } d->view->KexiDataAwareObjectInterface::data()->updateRowEditBuffer(item, COLUMN_ID_TYPE, TQVariant()); if (!addCommand) { d->addHistoryCommand_in_slotRowUpdated_enabled = true; d->addHistoryCommand_in_slotPropertyChanged_enabled = true; d->slotBeforeCellChanged_enabled = true; } d->view->KexiDataAwareObjectInterface::data()->saveRowChanges(*item, true); } void KexiTableDesignerView::insertField(int row, const TQString& caption, bool addCommand) { insertFieldInternal(row, 0, caption, addCommand); } void KexiTableDesignerView::insertField(int row, KoProperty::Set& set, bool addCommand) { insertFieldInternal(row, &set, TQString(), addCommand); } void KexiTableDesignerView::insertFieldInternal(int row, KoProperty::Set* set, //const KexiDB::Field& field, const TQString& 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->KexiDataAwareObjectInterface::data()->updateRowEditBuffer(item, COLUMN_ID_CAPTION, set ? (*set)["caption"].value() : TQVariant(caption));//field.caption()); d->view->KexiDataAwareObjectInterface::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->KexiDataAwareObjectInterface::data()->updateRowEditBuffer(item, COLUMN_ID_DESC, set ? (*set)["description"].value() : TQVariant());//field.description()); if (!addCommand) { d->slotBeforeCellChanged_enabled = true; } //this will create a new property set: d->view->KexiDataAwareObjectInterface::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->KexiDataAwareObjectInterface::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 TQCString& propertyName, const TQVariant& newValue, KoProperty::Property::ListData* const listData, bool addCommand ) { #ifdef KEXI_DEBUG_GUI KexiUtils::addAlterTableActionDebug(TQString("** changeFieldProperty: \"") + TQString(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->KexiDataAwareObjectInterface::data()->updateRowEditBuffer(item, COLUMN_ID_TYPE, int( KexiDB::Field::typeGroup( newValue.toInt() ) )-1); d->view->KexiDataAwareObjectInterface::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->KexiDataAwareObjectInterface::data()->updateRowEditBuffer(item, COLUMN_ID_CAPTION, newValue); d->view->KexiDataAwareObjectInterface::data()->saveRowChanges(*item); if (!addCommand) { d->slotBeforeCellChanged_enabled = true; } } else if (propertyName == "description") { if (!addCommand) { d->slotBeforeCellChanged_enabled = false; } d->view->KexiDataAwareObjectInterface::data()->updateRowEditBuffer(item, COLUMN_ID_DESC, newValue); if (!addCommand) { d->slotBeforeCellChanged_enabled = true; } d->view->KexiDataAwareObjectInterface::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 TQCString& propertyName, const TQVariant& 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"