diff --git a/src/core/kexiproject.cpp b/src/core/kexiproject.cpp index dfa6a8017..f1ec75138 100644 --- a/src/core/kexiproject.cpp +++ b/src/core/kexiproject.cpp @@ -1,1473 +1,1487 @@ /* This file is part of the KDE project Copyright (C) 2003 Lucijan Busch Copyright (C) 2003-2016 Jarosław 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 "kexiproject.h" #include "kexiprojectdata.h" #include "kexipartmanager.h" #include "kexipartitem.h" #include "kexipartinfo.h" #include "kexipart.h" #include "KexiWindow.h" #include "KexiWindowData.h" #include "kexi.h" #include "kexiblobbuffer.h" #include "kexiguimsghandler.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //! @return a real plugin ID for @a pluginId and @a partMime //! for compatibility with Kexi 1.x static QString realPluginId(const QString &pluginId, const QString &partMime) { if (pluginId.startsWith(QLatin1String("http://"))) { // for compatibility with Kexi 1.x // part mime was used at the time return QLatin1String("org.kexi-project.") + QString(partMime).remove("kexi/"); } return pluginId; } class Q_DECL_HIDDEN KexiProject::Private { public: explicit Private(KexiProject *qq) : q(qq) , connection(0) , data(0) , tempPartItemID_Counter(-1) , sqlParser(0) , versionMajor(0) , versionMinor(0) , privateIDCounter(0) , itemsRetrieved(false) { } ~Private() { delete data; data = 0; delete sqlParser; foreach(KexiPart::ItemDict* dict, itemDicts) { qDeleteAll(*dict); dict->clear(); } qDeleteAll(itemDicts); qDeleteAll(unstoredItems); unstoredItems.clear(); } void savePluginId(const QString& pluginId, int typeId) { if (!typeIds.contains(pluginId) && !pluginIdsForTypeIds.contains(typeId)) { typeIds.insert(pluginId, typeId); pluginIdsForTypeIds.insert(typeId, pluginId); } //! @todo what to do with extra plugin IDs for the same type ID or extra type ID name for the plugin ID? } //! @return user name for the current project //! @todo the name is taken from connection but it also can be specified otherwise //! if the same connection data is shared by multiple users. This will be especially //! true for 3-tier architectures. QString userName() const { QString name = connection->data().userName(); return name.isNull() ? "" : name; } bool setNameOrCaption(KexiPart::Item* item, const QString* _newName, const QString* _newCaption) { q->clearResult(); if (data->userMode()) { return false; } KexiUtils::WaitCursor wait; QString newName; if (_newName) { newName = _newName->trimmed(); KDbMessageTitleSetter ts(q); if (newName.isEmpty()) { q->m_result = KDbResult(xi18n("Could not set empty name for this object.")); return false; } if (q->itemForPluginId(item->pluginId(), newName) != 0) { q->m_result = KDbResult( xi18nc("@info", "Could not use this name. Object %1 already exists.", newName)); return false; } } QString newCaption; if (_newCaption) { newCaption = _newCaption->trimmed(); } KDbMessageTitleSetter et(q, xi18nc("@info", "Could not rename object %1.", item->name())); if (!q->checkWritable()) return false; KexiPart::Part *part = q->findPartFor(*item); if (!part) return false; KDbTransactionGuard tg(connection); if (!tg.transaction().isActive()) { q->m_result = connection->result(); return false; } if (_newName) { if (!part->rename(item, newName)) { q->m_result = KDbResult(part->lastOperationStatus().description); q->m_result.setMessageTitle(part->lastOperationStatus().message); return false; } if (!connection->executeSql(KDbEscapedString("UPDATE kexi__objects SET o_name=%1 WHERE o_id=%2") .arg(connection->escapeString(newName)) .arg(connection->driver()->valueToSql(KDbField::Integer, item->identifier())))) { q->m_result = connection->result(); return false; } } if (_newCaption) { if (!connection->executeSql(KDbEscapedString("UPDATE kexi__objects SET o_caption=%1 WHERE o_id=%2") .arg(connection->escapeString(newCaption)) .arg(connection->driver()->valueToSql(KDbField::Integer, item->identifier())))) { q->m_result = connection->result(); return false; } } if (!tg.commit()) { q->m_result = connection->result(); return false; } QString oldName(item->name()); if (_newName) { item->setName(newName); emit q->itemRenamed(*item, oldName); } QString oldCaption(item->caption()); if (_newCaption) { item->setCaption(newCaption); emit q->itemCaptionChanged(*item, oldCaption); } return true; } KexiProject *q; //! @todo KEXI3 use equivalent of QPointer KDbConnection* connection; //! @todo KEXI3 use equivalent of QPointer or make KexiProjectData implicitly shared like KDbConnectionData KexiProjectData *data; QString error_title; KexiPart::MissingPartsList missingParts; QHash typeIds; QHash pluginIdsForTypeIds; //! a cache for item() method, indexed by plugin IDs QHash itemDicts; QSet unstoredItems; //! helper for getting unique //! temporary identifiers for unstored items int tempPartItemID_Counter; KDbParser* sqlParser; int versionMajor; int versionMinor; int privateIDCounter; //!< counter: ID for private "document" like Relations window bool itemsRetrieved; }; //--------------------------- KexiProject::KexiProject(const KexiProjectData& pdata, KDbMessageHandler* handler) : QObject(), KDbObject(), KDbResultable() , d(new Private(this)) { d->data = new KexiProjectData(pdata); setMessageHandler(handler); } KexiProject::KexiProject(const KexiProjectData& pdata, KDbMessageHandler* handler, KDbConnection* conn) : QObject(), KDbObject(), KDbResultable() , d(new Private(this)) { d->data = new KexiProjectData(pdata); setMessageHandler(handler); if (*d->data->connectionData() == d->connection->data()) d->connection = conn; else qWarning() << "passed connection's data (" << conn->data().toUserVisibleString() << ") is not compatible with project's conn. data (" << d->data->connectionData()->toUserVisibleString() << ")"; } KexiProject::~KexiProject() { closeConnection(); delete d; } KDbConnection *KexiProject::dbConnection() const { return d->connection; } KexiProjectData* KexiProject::data() const { return d->data; } int KexiProject::versionMajor() const { return d->versionMajor; } int KexiProject::versionMinor() const { return d->versionMinor; } tristate KexiProject::open(bool *incompatibleWithKexi) { Q_ASSERT(incompatibleWithKexi); KDbMessageGuard mg(this); return openInternal(incompatibleWithKexi); } tristate KexiProject::open() { KDbMessageGuard mg(this); return openInternal(0); } tristate KexiProject::openInternal(bool *incompatibleWithKexi) { if (!Kexi::partManager().infoList()) { m_result = Kexi::partManager().result(); return cancelled; } if (incompatibleWithKexi) *incompatibleWithKexi = false; //qDebug() << d->data->databaseName() << d->data->connectionData()->driverId(); KDbMessageTitleSetter et(this, xi18nc("@info", "Could not open project %1.", d->data->databaseName())); if (!d->data->connectionData()->databaseName().isEmpty()) { QFileInfo finfo(d->data->connectionData()->databaseName()); if (!finfo.exists()) { KMessageBox::sorry(0, xi18nc("@info", "Could not open project. " "The project file %1 does not exist.", QDir::toNativeSeparators(finfo.absoluteFilePath())), xi18nc("@title:window", "Could Not Open File")); return cancelled; } if (!d->data->isReadOnly() && !finfo.isWritable()) { if (KexiProject::askForOpeningNonWritableFileAsReadOnly(0, finfo)) { d->data->setReadOnly(true); } else { return cancelled; } } } if (!createConnection()) { qWarning() << "!createConnection()"; return false; } bool cancel = false; if (!d->connection->useDatabase(d->data->databaseName(), true, &cancel)) { m_result = d->connection->result(); if (cancel) { return cancelled; } qWarning() << "!d->connection->useDatabase() " << d->data->databaseName() << " " << d->data->connectionData()->driverId(); if (d->connection->result().code() == ERR_NO_DB_PROPERTY) { // //! @todo this is temporary workaround as we have no import driver for SQLite if (/*supported?*/ !d->data->connectionData()->driverId().contains("sqlite")) { // if (incompatibleWithKexi) *incompatibleWithKexi = true; } else { KDbMessageTitleSetter et(this, xi18nc("@info (don't add tags around %1, it's done already)", "Database project %1 does not " "appear to have been created using Kexi and cannot be opened. " "It is an SQLite file created using other tools.", KexiUtils::localizedStringToHtmlSubstring(d->data->infoString()))); m_result = d->connection->result(); } closeConnectionInternal(); return false; } m_result = d->connection->result(); closeConnectionInternal(); return false; } if (!initProject()) return false; return createInternalStructures(/*insideTransaction*/true); } tristate KexiProject::create(bool forceOverwrite) { KDbMessageGuard mg(this); KDbMessageTitleSetter et(this, xi18nc("@info", "Could not create project %1.", d->data->databaseName())); if (!createConnection()) return false; if (!checkWritable()) return false; if (d->connection->databaseExists(d->data->databaseName())) { if (!forceOverwrite) return cancelled; if (!d->connection->dropDatabase(d->data->databaseName())) { m_result = d->connection->result(); closeConnectionInternal(); return false; } //qDebug() << "--- DB '" << d->data->databaseName() << "' dropped ---"; } if (!d->connection->createDatabase(d->data->databaseName())) { m_result = d->connection->result(); closeConnectionInternal(); return false; } //qDebug() << "--- DB '" << d->data->databaseName() << "' created ---"; // and now: open if (!d->connection->useDatabase(d->data->databaseName())) { qWarning() << "--- DB '" << d->data->databaseName() << "' USE ERROR ---"; m_result = d->connection->result(); closeConnectionInternal(); return false; } //qDebug() << "--- DB '" << d->data->databaseName() << "' used ---"; // KDbTransaction trans = d->connection->beginTransaction(); if (trans.isNull()) return false; if (!createInternalStructures(/*!insideTransaction*/false)) return false; //add some metadata //! @todo put more props. todo - creator, created date, etc. (also to KexiProjectData) KDbProperties props = d->connection->databaseProperties(); if (!props.setValue("kexiproject_major_ver", d->versionMajor) || !props.setCaption("kexiproject_major_ver", xi18n("Project major version")) || !props.setValue("kexiproject_minor_ver", d->versionMinor) || !props.setCaption("kexiproject_minor_ver", xi18n("Project minor version")) || !props.setValue("project_caption", d->data->caption()) || !props.setCaption("project_caption", xi18n("Project caption")) || !props.setValue("project_desc", d->data->description()) || !props.setCaption("project_desc", xi18n("Project description"))) { m_result = props.result(); return false; } if (trans.isActive() && !d->connection->commitTransaction(trans)) return false; // if (!Kexi::partManager().infoList()) { m_result = Kexi::partManager().result(); return cancelled; } return initProject(); } bool KexiProject::createInternalStructures(bool insideTransaction) { KDbTransactionGuard tg; if (insideTransaction) { tg.setTransaction(d->connection->beginTransaction()); if (tg.transaction().isNull()) return false; } //Get information about kexiproject version. //kexiproject version is a version of data layer above kexidb layer. KDbProperties props = d->connection->databaseProperties(); bool ok; int storedMajorVersion = props.value("kexiproject_major_ver").toInt(&ok); if (!ok) storedMajorVersion = 0; int storedMinorVersion = props.value("kexiproject_minor_ver").toInt(&ok); if (!ok) storedMinorVersion = 1; const tristate containsKexi__blobsTable = d->connection->containsTable("kexi__blobs"); if (~containsKexi__blobsTable) { return false; } int dummy; bool contains_o_folder_id = false; if (true == containsKexi__blobsTable) { const tristate res = d->connection->querySingleNumber( - KDbEscapedString("SELECT COUNT(o_folder_id) FROM kexi__blobs"), &dummy, 0, false/*addLimitTo1*/); + KDbEscapedString("SELECT COUNT(o_folder_id) FROM kexi__blobs"), &dummy, 0, + KDbConnection::QueryRecordOptions(KDbConnection::QueryRecordOption::Default) + & ~KDbConnection::QueryRecordOptions(KDbConnection::QueryRecordOption::AddLimitTo1)); if (res == false) { m_result = d->connection->result(); } else if (res == true) { contains_o_folder_id = true; } } bool add_folder_id_column = false; //! @todo what about read-only db access? if (storedMajorVersion <= 0) { d->versionMajor = KEXIPROJECT_VERSION_MAJOR; d->versionMinor = KEXIPROJECT_VERSION_MINOR; //For compatibility for projects created before Kexi 1.0 beta 1: //1. no kexiproject_major_ver and kexiproject_minor_ver -> add them if (!d->connection->options()->isReadOnly()) { if (!props.setValue("kexiproject_major_ver", d->versionMajor) || !props.setCaption("kexiproject_major_ver", xi18n("Project major version")) || !props.setValue("kexiproject_minor_ver", d->versionMinor) || !props.setCaption("kexiproject_minor_ver", xi18n("Project minor version"))) { return false; } } if (true == containsKexi__blobsTable) { //! @todo what to do for readonly connections? Should we alter kexi__blobs in memory? if (!d->connection->options()->isReadOnly()) { if (!contains_o_folder_id) { add_folder_id_column = true; } } } } if (storedMajorVersion != d->versionMajor || storedMajorVersion != d->versionMinor) { //! @todo version differs: should we change something? d->versionMajor = storedMajorVersion; d->versionMinor = storedMinorVersion; } QScopedPointer t_blobs(new KDbInternalTableSchema("kexi__blobs")); t_blobs->addField(new KDbField("o_id", KDbField::Integer, KDbField::PrimaryKey | KDbField::AutoInc, KDbField::Unsigned)); t_blobs->addField(new KDbField("o_data", KDbField::BLOB)); t_blobs->addField(new KDbField("o_name", KDbField::Text)); t_blobs->addField(new KDbField("o_caption", KDbField::Text)); t_blobs->addField(new KDbField("o_mime", KDbField::Text, KDbField::NotNull)); t_blobs->addField(new KDbField("o_folder_id", KDbField::Integer, 0, KDbField::Unsigned) //references kexi__gallery_folders.f_id //If null, the BLOB only points to virtual "All" folder //WILL BE USED in Kexi >=2.0 ); //*** create global BLOB container, if not present if (true == containsKexi__blobsTable) { if (add_folder_id_column && !d->connection->options()->isReadOnly()) { // 2. "kexi__blobs" table contains no "o_folder_id" column -> add it // (by copying table to avoid data loss) QScopedPointer kexi__blobsCopy( new KDbInternalTableSchema(*t_blobs)); //won't be not needed - will be physically renamed to kexi_blobs kexi__blobsCopy->setName("kexi__blobs__copy"); - if (!d->connection->createTable(kexi__blobsCopy.data(), true /*replaceExisting*/)) { + if (!d->connection->createTable(kexi__blobsCopy.data(), + KDbConnection::CreateTableOption::Default | KDbConnection::CreateTableOption::DropDestination)) + { + m_result = d->connection->result(); return false; } KDbInternalTableSchema *ts = kexi__blobsCopy.take(); // createTable() took ownerhip of kexi__blobsCopy // 2.1 copy data (insert 0's into o_folder_id column) - if (!d->connection->executeSql( - KDbEscapedString("INSERT INTO kexi__blobs (o_data, o_name, o_caption, o_mime, o_folder_id) " - "SELECT o_data, o_name, o_caption, o_mime, 0 FROM kexi__blobs")) - // 2.2 remove the original kexi__blobs - || !d->connection->executeSql(KDbEscapedString("DROP TABLE kexi__blobs")) //lowlevel - // 2.3 rename the copy back into kexi__blobs - || !d->connection->alterTableName(ts, "kexi__blobs", false /* no replace */) - ) { + if (!d->connection->executeSql(KDbEscapedString( + "INSERT INTO kexi__blobs (o_data, o_name, o_caption, o_mime, o_folder_id) " + "SELECT o_data, o_name, o_caption, o_mime, 0 FROM kexi__blobs")) + // 2.2 remove the original kexi__blobs + || !d->connection->executeSql( + KDbEscapedString("DROP TABLE kexi__blobs")) // lowlevel + // 2.3 rename the copy back into kexi__blobs + || !d->connection->alterTableName( + ts, "kexi__blobs", + KDbConnection::AlterTableNameOptions(KDbConnection::AlterTableNameOption::Default) + & ~KDbConnection::AlterTableNameOptions(KDbConnection::AlterTableNameOption::DropDestination))) + { //(no need to drop the copy, ROLLBACK will drop it) m_result = d->connection->result(); return false; } } //! just insert this schema, proper table exists d->connection->createTable(t_blobs.take()); } else { if (!d->connection->options()->isReadOnly()) { - if (!d->connection->createTable(t_blobs.data(), true/*replaceExisting*/)) { + if (!d->connection->createTable(t_blobs.data(), + KDbConnection::CreateTableOption::Default | KDbConnection::CreateTableOption::DropDestination)) + { m_result = d->connection->result(); return false; } (void)t_blobs.take(); // createTable() took ownerhip of t_blobs } } //Store default part information. //Information for other parts (forms, reports...) are created on demand in KexiWindow::storeNewData() const tristate containsKexi__partsTable = d->connection->containsTable("kexi__parts"); if (~containsKexi__partsTable) { return false; } QScopedPointer t_parts(new KDbInternalTableSchema("kexi__parts")); t_parts->addField( new KDbField("p_id", KDbField::Integer, KDbField::PrimaryKey | KDbField::AutoInc, KDbField::Unsigned) ); t_parts->addField(new KDbField("p_name", KDbField::Text)); t_parts->addField(new KDbField("p_mime", KDbField::Text)); t_parts->addField(new KDbField("p_url", KDbField::Text)); if (true == containsKexi__partsTable) { //! just insert this schema d->connection->createTable(t_parts.take()); } else { if (!d->connection->options()->isReadOnly()) { - bool partsTableOk = d->connection->createTable(t_parts.data(), true/*replaceExisting*/); + bool partsTableOk = d->connection->createTable(t_parts.data(), + KDbConnection::CreateTableOption::Default | KDbConnection::CreateTableOption::DropDestination); if (!partsTableOk) { m_result = d->connection->result(); return false; } KDbInternalTableSchema *ts = t_parts.take(); // createTable() took ownerhip of t_parts QScopedPointer fl(ts->subList("p_id", "p_name", "p_mime", "p_url")); #define INSERT_RECORD(typeId, groupName, name) \ if (partsTableOk) { \ partsTableOk = d->connection->insertRecord(fl.data(), QVariant(int(KexiPart::typeId)), \ QVariant(groupName), \ QVariant("kexi/" name), QVariant("org.kexi-project." name)); \ if (partsTableOk) { \ d->savePluginId("org.kexi-project." name, int(KexiPart::typeId)); \ } \ } INSERT_RECORD(TableObjectType, "Tables", "table") INSERT_RECORD(QueryObjectType, "Queries", "query") INSERT_RECORD(FormObjectType, "Forms", "form") INSERT_RECORD(ReportObjectType, "Reports", "report") INSERT_RECORD(ScriptObjectType, "Scripts", "script") INSERT_RECORD(WebObjectType, "Web pages", "web") INSERT_RECORD(MacroObjectType, "Macros", "macro") #undef INSERT_RECORD if (!partsTableOk) { m_result = d->connection->result(); // note: kexi__parts object still exists because createTable() succeeded return false; } } } // User data storage const tristate containsKexi__userdataTable = d->connection->containsTable("kexi__userdata"); if (~containsKexi__userdataTable) { return false; } QScopedPointer t_userdata(new KDbInternalTableSchema("kexi__userdata")); t_userdata->addField(new KDbField("d_user", KDbField::Text, KDbField::NotNull)); t_userdata->addField(new KDbField("o_id", KDbField::Integer, KDbField::NotNull, KDbField::Unsigned)); t_userdata->addField(new KDbField("d_sub_id", KDbField::Text, KDbField::NotNull | KDbField::NotEmpty)); t_userdata->addField(new KDbField("d_data", KDbField::LongText)); if (true == containsKexi__userdataTable) { d->connection->createTable(t_userdata.take()); } else if (!d->connection->options()->isReadOnly()) { - if (!d->connection->createTable(t_userdata.data(), true/*replaceExisting*/)) { + if (!d->connection->createTable(t_userdata.data(), + KDbConnection::CreateTableOption::Default | KDbConnection::CreateTableOption::DropDestination)) + { m_result = d->connection->result(); return false; } (void)t_userdata.take(); // createTable() took ownerhip of t_userdata } if (insideTransaction) { if (tg.transaction().isActive() && !tg.commit()) { m_result = d->connection->result(); return false; } } return true; } bool KexiProject::createConnection() { clearResult(); KDbMessageGuard mg(this); if (d->connection) { return true; } KDbMessageTitleSetter et(this); KDbDriver *driver = Kexi::driverManager().driver(d->data->connectionData()->driverId()); if (!driver) { m_result = Kexi::driverManager().result(); return false; } KDbConnectionOptions connectionOptions; if (d->data->isReadOnly()) { connectionOptions.setReadOnly(true); } d->connection = driver->createConnection(*d->data->connectionData(), connectionOptions); if (!d->connection) { m_result = driver->result(); qWarning() << "error create connection: " << m_result; return false; } if (!d->connection->connect()) { m_result = d->connection->result(); qWarning() << "error connecting: " << m_result; delete d->connection; //this will also clear connection for BLOB buffer d->connection = 0; return false; } //re-init BLOB buffer //! @todo won't work for subsequent connection KexiBLOBBuffer::setConnection(d->connection); return true; } bool KexiProject::closeConnectionInternal() { if (!m_result.isError()) { clearResult(); } if (!d->connection) { return true; } if (!d->connection->disconnect()) { if (!m_result.isError()) { m_result = d->connection->result(); } return false; } delete d->connection; //this will also clear connection for BLOB buffer d->connection = 0; return true; } bool KexiProject::closeConnection() { clearResult(); KDbMessageGuard mg(this); if (!d->connection) return true; if (!d->connection->disconnect()) { m_result = d->connection->result(); return false; } delete d->connection; //this will also clear connection for BLOB buffer d->connection = 0; return true; } bool KexiProject::initProject() { //qDebug() << "checking project parts..."; if (!checkProject()) { return false; } // !@todo put more props. todo - creator, created date, etc. (also to KexiProjectData) KDbProperties props = d->connection->databaseProperties(); QString str(props.value("project_caption").toString()); if (!str.isEmpty()) d->data->setCaption(str); str = props.value("project_desc").toString(); if (!str.isEmpty()) d->data->setDescription(str); return true; } bool KexiProject::isConnected() { if (d->connection && d->connection->isDatabaseUsed()) return true; return false; } KexiPart::ItemDict* KexiProject::items(KexiPart::Info *i) { clearResult(); KDbMessageGuard mg(this); if (!i || !isConnected()) return 0; //trying in cache... KexiPart::ItemDict *dict = d->itemDicts.value(i->id()); if (dict) return dict; if (d->itemsRetrieved) return 0; if (!retrieveItems()) return 0; return items(i); // try again } bool KexiProject::retrieveItems() { d->itemsRetrieved = true; KDbCursor *cursor = d->connection->executeQuery( KDbEscapedString("SELECT o_id, o_name, o_caption, o_type FROM kexi__objects ORDER BY o_type")); if (!cursor) { m_result = d->connection->result(); return 0; } int recentTypeId = -1000; QString pluginId; KexiPart::ItemDict *dict = 0; for (cursor->moveFirst(); !cursor->eof(); cursor->moveNext()) { bool ok; const int typeId = cursor->value(3).toInt(&ok); if (!ok || typeId <= 0) { qInfo() << "object of unknown type id" << cursor->value(3) << "id=" << cursor->value(0) << "name=" << cursor->value(1); continue; } if (recentTypeId == typeId) { if (pluginId.isEmpty()) // still the same unknown plugin ID continue; } else { // a new type ID: create another plugin items dict if it's an ID for a known type recentTypeId = typeId; pluginId = pluginIdForTypeId(typeId); if (pluginId.isEmpty()) continue; dict = new KexiPart::ItemDict(); d->itemDicts.insert(pluginId, dict); } const int ident = cursor->value(0).toInt(&ok); const QString objName(cursor->value(1).toString()); if (ok && (ident > 0) && !d->connection->isInternalTableSchema(objName) && KDb::isIdentifier(objName)) { KexiPart::Item *it = new KexiPart::Item(); it->setIdentifier(ident); it->setPluginId(pluginId); it->setName(objName); it->setCaption(cursor->value(2).toString()); dict->insert(it->identifier(), it); } //qDebug() << "ITEM ADDED == "<* staticObjectArgs) { clearResult(); KDbMessageGuard mg(this); if (viewMode != Kexi::DataViewMode && data()->userMode()) return 0; KDbMessageTitleSetter et(this); KexiPart::Part *part = findPartFor(*item); if (!part) return 0; KexiWindow *window = part->openInstance(parent, item, viewMode, staticObjectArgs); if (!window) { if (part->lastOperationStatus().error()) m_result = KDbResult(xi18nc("@info", "Opening object %1 failed.\n%2%3", item->name()) .arg(part->lastOperationStatus().message) .arg(part->lastOperationStatus().description) .replace("(I18N_ARGUMENT_MISSING)", " ")); // a hack until there's other solution return 0; } return window; } KexiWindow* KexiProject::openObject(QWidget* parent, const QString &pluginId, const QString& name, Kexi::ViewMode viewMode) { KexiPart::Item *it = itemForPluginId(pluginId, name); return it ? openObject(parent, it, viewMode) : 0; } bool KexiProject::checkWritable() { if (!d->connection->options()->isReadOnly()) return true; m_result = KDbResult(xi18n("This project is opened as read only.")); return false; } bool KexiProject::removeObject(KexiPart::Item *item) { Q_ASSERT(item); clearResult(); if (data()->userMode()) return false; KDbMessageTitleSetter et(this); if (!checkWritable()) return false; KexiPart::Part *part = findPartFor(*item); if (!part) return false; if (!item->neverSaved() && !part->remove(item)) { //! @todo check for errors return false; } if (!item->neverSaved()) { KDbTransactionGuard tg(d->connection); if (!tg.transaction().isActive()) { m_result = d->connection->result(); return false; } if (!d->connection->removeObject(item->identifier())) { m_result = d->connection->result(); return false; } if (!removeUserDataBlock(item->identifier())) { m_result = KDbResult(ERR_DELETE_SERVER_ERROR, xi18n("Could not remove object's user data.")); return false; } if (!tg.commit()) { m_result = d->connection->result(); return false; } } emit itemRemoved(*item); //now: remove this item from cache if (part->info()) { KexiPart::ItemDict *dict = d->itemDicts.value(part->info()->pluginId()); if (!(dict && dict->remove(item->identifier()))) d->unstoredItems.remove(item);//remove temp. } return true; } bool KexiProject::renameObject(KexiPart::Item *item, const QString& newName) { KDbMessageGuard mg(this); return d->setNameOrCaption(item, &newName, 0); } bool KexiProject::setObjectCaption(KexiPart::Item *item, const QString& newCaption) { KDbMessageGuard mg(this); return d->setNameOrCaption(item, 0, &newCaption); } KexiPart::Item* KexiProject::createPartItem(KexiPart::Info *info, const QString& suggestedCaption) { clearResult(); KDbMessageGuard mg(this); if (data()->userMode()) return 0; KDbMessageTitleSetter et(this); KexiPart::Part *part = Kexi::partManager().part(info); if (!part) { m_result = Kexi::partManager().result(); return 0; } KexiPart::ItemDict *dict = items(info); if (!dict) { dict = new KexiPart::ItemDict(); d->itemDicts.insert(info->pluginId(), dict); } QSet storedItemNames; foreach(KexiPart::Item* item, *dict) { storedItemNames.insert(item->name()); } QSet unstoredItemNames; foreach(KexiPart::Item* item, d->unstoredItems) { unstoredItemNames.insert(item->name()); } //find new, unique default name for this item int n; QString new_name; QString base_name; if (suggestedCaption.isEmpty()) { n = 1; base_name = part->instanceName(); } else { n = 0; //means: try not to add 'n' base_name = KDb::stringToIdentifier(suggestedCaption).toLower(); } base_name = KDb::stringToIdentifier(base_name).toLower(); do { new_name = base_name; if (n >= 1) new_name += QString::number(n); if (storedItemNames.contains(new_name)) { n++; continue; //stored exists! } if (!unstoredItemNames.contains(new_name)) break; //unstored doesn't exist n++; } while (n < 1000/*sanity*/); if (n >= 1000) return 0; QString new_caption(suggestedCaption.isEmpty() ? part->info()->name() : suggestedCaption); if (n >= 1) new_caption += QString::number(n); KexiPart::Item *item = new KexiPart::Item(); item->setIdentifier(--d->tempPartItemID_Counter); //temporary item->setPluginId(info->pluginId()); item->setName(new_name); item->setCaption(new_caption); item->setNeverSaved(true); d->unstoredItems.insert(item); return item; } KexiPart::Item* KexiProject::createPartItem(KexiPart::Part *part, const QString& suggestedCaption) { Q_ASSERT(part); return createPartItem(part->info(), suggestedCaption); } void KexiProject::deleteUnstoredItem(KexiPart::Item *item) { if (!item) return; d->unstoredItems.remove(item); delete item; } KDbParser* KexiProject::sqlParser() { if (!d->sqlParser) { if (!d->connection) return 0; d->sqlParser = new KDbParser(d->connection); } return d->sqlParser; } const char warningNoUndo[] = I18N_NOOP2("warning", "Entire project's data and design will be removed."); /*static*/ KexiProject* KexiProject::createBlankProject(bool *cancelled, const KexiProjectData& data, KDbMessageHandler* handler) { Q_ASSERT(cancelled); *cancelled = false; KexiProject *prj = new KexiProject(data, handler); tristate res = prj->create(false); if (~res) { //! @todo move to KexiMessageHandler if (KMessageBox::Yes != KMessageBox::warningYesNo(0, xi18nc("@info (don't add tags around %1, it's done already)", "The project %1 already exists." "Do you want to replace it with a new, blank one?" "%2", KexiUtils::localizedStringToHtmlSubstring(prj->data()->infoString()), xi18n(warningNoUndo)), QString(), KGuiItem(xi18nc("@action:button", "Replace")), KStandardGuiItem::cancel())) //! @todo add toUserVisibleString() for server-based prj { delete prj; *cancelled = true; return 0; } res = prj->create(true/*overwrite*/); } if (res != true) { delete prj; return 0; } //qDebug() << "new project created --- "; //! @todo Kexi::recentProjects().addProjectData( data ); return prj; } /*static*/ tristate KexiProject::dropProject(const KexiProjectData& data, KDbMessageHandler* handler, bool dontAsk) { if (!dontAsk && KMessageBox::Yes != KMessageBox::questionYesNo(0, xi18nc("@info", "Do you want to delete the project %1?" "%2", static_cast(&data)->name(), i18n(warningNoUndo)), QString(), KGuiItem(xi18nc("@action:button", "Delete Project"), koIconName("edit-delete")), KStandardGuiItem::no(), QString(), KMessageBox::Notify | KMessageBox::Dangerous)) { return cancelled; } KexiProject prj(data, handler); if (!prj.open()) return false; if (prj.dbConnection()->options()->isReadOnly()) { handler->showErrorMessage( KDbMessageHandler::Error, xi18n("Could not delete this project. Database connection for this project has been opened as read only.")); return false; } KDbMessageGuard mg(prj.dbConnection()->result(), handler); return prj.dbConnection()->dropDatabase(); } bool KexiProject::checkProject(const QString& singlePluginId) { clearResult(); //! @todo catch errors! if (!d->connection->isDatabaseUsed()) { m_result = d->connection->result(); return false; } const tristate containsKexi__partsTable = d->connection->containsTable("kexi__parts"); if (~containsKexi__partsTable) { return false; } if (true == containsKexi__partsTable) { // check if kexi__parts exists, if missing, createInternalStructures() will create it KDbEscapedString sql = KDbEscapedString("SELECT p_id, p_name, p_mime, p_url FROM kexi__parts ORDER BY p_id"); if (!singlePluginId.isEmpty()) { sql.append(KDbEscapedString(" WHERE p_url=%1").arg(d->connection->escapeString(singlePluginId))); } KDbCursor *cursor = d->connection->executeQuery(sql); if (!cursor) { m_result = d->connection->result(); return false; } bool saved = false; for (cursor->moveFirst(); !cursor->eof(); cursor->moveNext()) { const QString partMime(cursor->value(2).toString()); QString pluginId(cursor->value(3).toString()); pluginId = realPluginId(pluginId, partMime); if (pluginId == QLatin1String("uk.co.piggz.report")) { // compatibility pluginId = QLatin1String("org.kexi-project.report"); } KexiPart::Info *info = Kexi::partManager().infoForPluginId(pluginId); bool ok; const int typeId = cursor->value(0).toInt(&ok); if (!ok || typeId <= 0) { qWarning() << "Invalid type ID" << typeId << "; part with ID" << pluginId << "will not be used"; } if (info && ok && typeId > 0) { d->savePluginId(pluginId, typeId); saved = true; } else { KexiPart::MissingPart m; m.name = cursor->value(1).toString(); m.id = pluginId; d->missingParts.append(m); } } d->connection->deleteCursor(cursor); if (!saved && !singlePluginId.isEmpty()) { return false; // failure is single part class was not found } } return true; } int KexiProject::generatePrivateID() { return --d->privateIDCounter; } bool KexiProject::createIdForPart(const KexiPart::Info& info) { KDbMessageGuard mg(this); int typeId = typeIdForPluginId(info.pluginId()); if (typeId > 0) { return true; } // try again, perhaps the id is already created if (checkProject(info.pluginId())) { return true; } // Find first available custom part ID by taking the greatest // existing custom ID (if it exists) and adding 1. typeId = int(KexiPart::UserObjectType); tristate success = d->connection->querySingleNumber(KDbEscapedString("SELECT max(p_id) FROM kexi__parts"), &typeId); if (!success) { // Couldn't read part id's from the kexi__parts table m_result = d->connection->result(); return false; } else { // Got a maximum part ID, or there were no parts typeId = typeId + 1; typeId = qMax(typeId, (int)KexiPart::UserObjectType); } //this part's ID is not stored within kexi__parts: KDbTableSchema *ts = d->connection->tableSchema("kexi__parts"); if (!ts) { m_result = d->connection->result(); return false; } QScopedPointer fl(ts->subList("p_id", "p_name", "p_mime", "p_url")); //qDebug() << "fieldlist: " << (fl ? *fl : QString()); if (!fl) return false; //qDebug() << info.ptr()->untranslatedGenericName(); // QStringList sl = part()->info()->ptr()->propertyNames(); // for (QStringList::ConstIterator it=sl.constBegin();it!=sl.constEnd();++it) //qDebug() << *it << " " << part()->info()->ptr()->property(*it).toString(); if (!d->connection->insertRecord( fl.data(), QVariant(typeId), QVariant(info.untranslatedGroupName()), QVariant(QString::fromLatin1("kexi/") + info.typeName()/*ok?*/), QVariant(info.id() /*always ok?*/))) { m_result = d->connection->result(); return false; } //qDebug() << "insert success!"; d->savePluginId(info.id(), typeId); //qDebug() << "new id is: " << p_id; return true; } KexiPart::MissingPartsList KexiProject::missingParts() const { return d->missingParts; } static bool checkObjectId(const char* method, int objectID) { if (objectID <= 0) { qWarning() << method << ": Invalid objectID" << objectID; return false; } return true; } tristate KexiProject::loadUserDataBlock(int objectID, const QString& dataID, QString *dataString) { KDbMessageGuard mg(this); if (!checkObjectId("loadUserDataBlock", objectID)) { return false; } if (!d->connection->querySingleString( KDbEscapedString("SELECT d_data FROM kexi__userdata WHERE o_id=%1 AND ") .arg(d->connection->driver()->valueToSql(KDbField::Integer, objectID)) + KDb::sqlWhere(d->connection->driver(), KDbField::Text, "d_user", d->userName()) + " AND " + KDb::sqlWhere(d->connection->driver(), KDbField::Text, "d_sub_id", dataID), dataString)) { m_result = d->connection->result(); return false; } return true; } bool KexiProject::storeUserDataBlock(int objectID, const QString& dataID, const QString &dataString) { KDbMessageGuard mg(this); if (!checkObjectId("storeUserDataBlock", objectID)) { return false; } KDbEscapedString sql = KDbEscapedString("SELECT kexi__userdata.o_id FROM kexi__userdata WHERE o_id=%1").arg(objectID); KDbEscapedString sql_sub = KDb::sqlWhere(d->connection->driver(), KDbField::Text, "d_user", d->userName()) + " AND " + KDb::sqlWhere(d->connection->driver(), KDbField::Text, "d_sub_id", dataID); const tristate result = d->connection->resultExists(sql + " AND " + sql_sub); if (~result) { m_result = d->connection->result(); return false; } if (result == true) { if (!d->connection->executeSql( KDbEscapedString("UPDATE kexi__userdata SET d_data=" + d->connection->driver()->valueToSql(KDbField::LongText, dataString) + " WHERE o_id=" + QString::number(objectID) + " AND " + sql_sub))) { m_result = d->connection->result(); return false; } return true; } if (!d->connection->executeSql( KDbEscapedString("INSERT INTO kexi__userdata (d_user, o_id, d_sub_id, d_data) VALUES (") + d->connection->driver()->valueToSql(KDbField::Text, d->userName()) + ", " + QString::number(objectID) + ", " + d->connection->driver()->valueToSql(KDbField::Text, dataID) + ", " + d->connection->driver()->valueToSql(KDbField::LongText, dataString) + ")")) { m_result = d->connection->result(); return false; } return true; } bool KexiProject::copyUserDataBlock(int sourceObjectID, int destObjectID, const QString &dataID) { KDbMessageGuard mg(this); if (!checkObjectId("storeUserDataBlock(sourceObjectID)", sourceObjectID)) { return false; } if (!checkObjectId("storeUserDataBlock(destObjectID)", destObjectID)) { return false; } if (sourceObjectID == destObjectID) return true; if (!removeUserDataBlock(destObjectID, dataID)) // remove before copying return false; KDbEscapedString sql = KDbEscapedString("INSERT INTO kexi__userdata SELECT t.d_user, %2, t.d_sub_id, t.d_data " "FROM kexi__userdata AS t WHERE d_user=%1 AND o_id=%3") .arg(d->connection->escapeString(d->userName())) .arg(d->connection->driver()->valueToSql(KDbField::Integer, destObjectID)) .arg(d->connection->driver()->valueToSql(KDbField::Integer, sourceObjectID)); if (!dataID.isEmpty()) { sql += " AND " + KDb::sqlWhere(d->connection->driver(), KDbField::Text, "d_sub_id", dataID); } if (!d->connection->executeSql(sql)) { m_result = d->connection->result(); return false; } return true; } bool KexiProject::removeUserDataBlock(int objectID, const QString& dataID) { KDbMessageGuard mg(this); if (!checkObjectId("removeUserDataBlock", objectID)) { return false; } if (dataID.isEmpty()) { if (!KDb::deleteRecords(d->connection, "kexi__userdata", "o_id", KDbField::Integer, objectID, "d_user", KDbField::Text, d->userName())) { m_result = d->connection->result(); return false; } else if (!KDb::deleteRecords(d->connection, "kexi__userdata", "o_id", KDbField::Integer, objectID, "d_user", KDbField::Text, d->userName(), "d_sub_id", KDbField::Text, dataID)) { m_result = d->connection->result(); return false; } } return true; } // static bool KexiProject::askForOpeningNonWritableFileAsReadOnly(QWidget *parent, const QFileInfo &finfo) { KGuiItem openItem(KStandardGuiItem::open()); openItem.setText(xi18n("Open As Read Only")); return KMessageBox::Yes == KMessageBox::questionYesNo( parent, xi18nc("@info", "Could not open file %1 for reading and writing." "Do you want to open the file as read only?", QDir::toNativeSeparators(finfo.filePath())), xi18nc("@title:window", "Could Not Open File" ), openItem, KStandardGuiItem::cancel(), QString()); } diff --git a/src/migration/importtablewizard.cpp b/src/migration/importtablewizard.cpp index 0a0574a75..3c131a8cd 100644 --- a/src/migration/importtablewizard.cpp +++ b/src/migration/importtablewizard.cpp @@ -1,768 +1,770 @@ /* This file is part of the KDE project Copyright (C) 2009 Adam Pigg Copyright (C) 2014-2016 Jarosław Staniek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. 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 "importtablewizard.h" #include "migratemanager.h" #include "keximigrate.h" #include "keximigratedata.h" #include "AlterSchemaWidget.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 #include #include #include #include #include #include #include using namespace KexiMigration; #define RECORDS_FOR_PREVIEW 3 ImportTableWizard::ImportTableWizard ( KDbConnection* curDB, QWidget* parent, QMap* args, Qt::WindowFlags flags) : KAssistantDialog ( parent, flags ), m_args(args) { m_connection = curDB; m_migrateDriver = 0; m_prjSet = 0; m_importComplete = false; m_importWasCanceled = false; KexiMainWindowIface::global()->setReasonableDialogSize(this); setupIntroPage(); setupSrcConn(); setupSrcDB(); setupTableSelectPage(); setupAlterTablePage(); setupImportingPage(); setupProgressPage(); setupFinishPage(); setValid(m_srcConnPageItem, false); connect(this, SIGNAL(currentPageChanged(KPageWidgetItem*,KPageWidgetItem*)), this, SLOT(slot_currentPageChanged(KPageWidgetItem*,KPageWidgetItem*))); //! @todo Change this to message prompt when we move to non-dialog wizard. connect(m_srcConnSel, SIGNAL(connectionSelected(bool)), this, SLOT(slotConnPageItemSelected(bool))); } ImportTableWizard::~ImportTableWizard() { delete m_prjSet; delete m_srcConnSel; } void ImportTableWizard::back() { KAssistantDialog::back(); } void ImportTableWizard::next() { if (currentPage() == m_srcConnPageItem) { if (fileBasedSrcSelected()) { setAppropriate(m_srcDBPageItem, false); } else { setAppropriate(m_srcDBPageItem, true); } } else if (currentPage() == m_alterTablePageItem) { if (m_alterSchemaWidget->nameExists(m_alterSchemaWidget->nameWidget()->nameText())) { KMessageBox::information(this, xi18nc("@info", "%1 name is already used by an existing table. " "Enter different table name to continue.", m_alterSchemaWidget->nameWidget()->nameText()), xi18n("Name Already Used")); return; } } KAssistantDialog::next(); } void ImportTableWizard::accept() { if (m_args) { if (m_finishCheckBox->isChecked()) { m_args->insert("destinationTableName",m_alterSchemaWidget->nameWidget()->nameText()); } else { m_args->remove("destinationTableName"); } } QDialog::accept(); } void ImportTableWizard::reject() { QDialog::reject(); } //=========================================================== // void ImportTableWizard::setupIntroPage() { m_introPageWidget = new QWidget(this); QVBoxLayout *vbox = new QVBoxLayout(); m_introPageWidget->setLayout(vbox); KexiUtils::setStandardMarginsAndSpacing(vbox); QLabel *lblIntro = new QLabel(m_introPageWidget); lblIntro->setAlignment(Qt::AlignTop | Qt::AlignLeft); lblIntro->setWordWrap(true); lblIntro->setText( xi18nc("@info", "Table Importing Assistant allows you to import a table from an existing " "database into the current Kexi project." "Click Next button to continue or " "Cancel button to exit this assistant.")); vbox->addWidget(lblIntro); m_introPageItem = new KPageWidgetItem(m_introPageWidget, xi18n("Welcome to the Table Importing Assistant")); addPage(m_introPageItem); } void ImportTableWizard::setupSrcConn() { m_srcConnPageWidget = new QWidget(this); QVBoxLayout *vbox = new QVBoxLayout(m_srcConnPageWidget); KexiUtils::setStandardMarginsAndSpacing(vbox); m_srcConnSel = new KexiConnectionSelectorWidget(&Kexi::connset(), "kfiledialog:///ProjectMigrationSourceDir", KFileWidget::Opening, m_srcConnPageWidget); m_srcConnSel->hideConnectonIcon(); m_srcConnSel->showSimpleConnection(); QSet excludedFilters; //! @todo remove when support for kexi files as source prj is added in migration excludedFilters << KDb::defaultFileBasedDriverMimeType() << "application/x-kexiproject-shortcut" << "application/x-kexi-connectiondata"; m_srcConnSel->fileWidget->setExcludedFilters(excludedFilters); vbox->addWidget(m_srcConnSel); m_srcConnPageItem = new KPageWidgetItem(m_srcConnPageWidget, xi18n("Select Location for Source Database")); addPage(m_srcConnPageItem); } void ImportTableWizard::setupSrcDB() { // arrivesrcdbPage creates widgets on that page m_srcDBPageWidget = new QWidget(this); m_srcDBName = NULL; m_srcDBPageItem = new KPageWidgetItem(m_srcDBPageWidget, xi18n("Select Source Database")); addPage(m_srcDBPageItem); } void ImportTableWizard::setupTableSelectPage() { m_tablesPageWidget = new QWidget(this); QVBoxLayout *vbox = new QVBoxLayout(m_tablesPageWidget); KexiUtils::setStandardMarginsAndSpacing(vbox); m_tableListWidget = new QListWidget(this); m_tableListWidget->setSelectionMode(QAbstractItemView::SingleSelection); connect(m_tableListWidget, SIGNAL(itemSelectionChanged()), this, SLOT(slotTableListWidgetSelectionChanged())); vbox->addWidget(m_tableListWidget); m_tablesPageItem = new KPageWidgetItem(m_tablesPageWidget, xi18n("Select the Table to Import")); addPage(m_tablesPageItem); } //=========================================================== // void ImportTableWizard::setupImportingPage() { m_importingPageWidget = new QWidget(this); m_importingPageWidget->hide(); QVBoxLayout *vbox = new QVBoxLayout(m_importingPageWidget); KexiUtils::setStandardMarginsAndSpacing(vbox); m_lblImportingTxt = new QLabel(m_importingPageWidget); m_lblImportingTxt->setAlignment(Qt::AlignTop | Qt::AlignLeft); m_lblImportingTxt->setWordWrap(true); m_lblImportingErrTxt = new QLabel(m_importingPageWidget); m_lblImportingErrTxt->setAlignment(Qt::AlignTop | Qt::AlignLeft); m_lblImportingErrTxt->setWordWrap(true); vbox->addWidget(m_lblImportingTxt); vbox->addWidget(m_lblImportingErrTxt); vbox->addStretch(1); QWidget *options_widget = new QWidget(m_importingPageWidget); vbox->addWidget(options_widget); QVBoxLayout *options_vbox = new QVBoxLayout(options_widget); options_vbox->setSpacing(KexiUtils::spacingHint()); m_importOptionsButton = new QPushButton(koIcon("configure"), xi18n("Advanced Options"), options_widget); connect(m_importOptionsButton, SIGNAL(clicked()),this, SLOT(slotOptionsButtonClicked())); options_vbox->addWidget(m_importOptionsButton); options_vbox->addStretch(1); m_importingPageWidget->show(); m_importingPageItem = new KPageWidgetItem(m_importingPageWidget, xi18n("Importing")); addPage(m_importingPageItem); } void ImportTableWizard::setupAlterTablePage() { m_alterTablePageWidget = new QWidget(this); m_alterTablePageWidget->hide(); QVBoxLayout *vbox = new QVBoxLayout(m_alterTablePageWidget); KexiUtils::setStandardMarginsAndSpacing(vbox); m_alterSchemaWidget = new KexiMigration::AlterSchemaWidget(this); vbox->addWidget(m_alterSchemaWidget); m_alterTablePageWidget->show(); m_alterTablePageItem = new KPageWidgetItem(m_alterTablePageWidget, xi18n("Alter the Detected Table Design")); addPage(m_alterTablePageItem); } void ImportTableWizard::setupProgressPage() { m_progressPageWidget = new QWidget(this); m_progressPageWidget->hide(); QVBoxLayout *vbox = new QVBoxLayout(m_progressPageWidget); KexiUtils::setStandardMarginsAndSpacing(vbox); m_progressPageWidget->setLayout(vbox); m_progressLbl = new QLabel(m_progressPageWidget); m_progressLbl->setAlignment(Qt::AlignTop | Qt::AlignLeft); m_progressLbl->setWordWrap(true); m_rowsImportedLbl = new QLabel(m_progressPageWidget); m_importingProgressBar = new QProgressBar(m_progressPageWidget); m_importingProgressBar->setMinimum(0); m_importingProgressBar->setMaximum(0); m_importingProgressBar->setValue(0); vbox->addWidget(m_progressLbl); vbox->addWidget(m_rowsImportedLbl); vbox->addWidget(m_importingProgressBar); vbox->addStretch(1); m_progressPageItem = new KPageWidgetItem(m_progressPageWidget, xi18n("Processing Import")); addPage(m_progressPageItem); } void ImportTableWizard::setupFinishPage() { m_finishPageWidget = new QWidget(this); m_finishPageWidget->hide(); QVBoxLayout *vbox = new QVBoxLayout(m_finishPageWidget); KexiUtils::setStandardMarginsAndSpacing(vbox); m_finishLbl = new QLabel(m_finishPageWidget); m_finishLbl->setAlignment(Qt::AlignTop | Qt::AlignLeft); m_finishLbl->setWordWrap(true); vbox->addWidget(m_finishLbl); m_finishCheckBox = new QCheckBox(xi18n("Open imported table"), m_finishPageWidget); m_finishCheckBox->setChecked(true); vbox->addSpacing(KexiUtils::spacingHint()); vbox->addWidget(m_finishCheckBox); vbox->addStretch(1); m_finishPageItem = new KPageWidgetItem(m_finishPageWidget, xi18n("Success")); addPage(m_finishPageItem); } void ImportTableWizard::slot_currentPageChanged(KPageWidgetItem* curPage,KPageWidgetItem* prevPage) { Q_UNUSED(prevPage); if (curPage == m_introPageItem) { } else if (curPage == m_srcConnPageItem) { arriveSrcConnPage(); } else if (curPage == m_srcDBPageItem) { arriveSrcDBPage(); } else if (curPage == m_tablesPageItem) { arriveTableSelectPage(prevPage); } else if (curPage == m_alterTablePageItem) { if (prevPage == m_tablesPageItem) { arriveAlterTablePage(); } } else if (curPage == m_importingPageItem) { arriveImportingPage(); } else if (curPage == m_progressPageItem) { arriveProgressPage(); } else if (curPage == m_finishPageItem) { arriveFinishPage(); } } void ImportTableWizard::arriveSrcConnPage() { } void ImportTableWizard::arriveSrcDBPage() { if (fileBasedSrcSelected()) { //! @todo Back button doesn't work after selecting a file to import } else if (!m_srcDBName) { m_srcDBPageWidget->hide(); qDebug() << "Looks like we need a project selector widget!"; KDbConnectionData* conndata = m_srcConnSel->selectedConnectionData(); if (conndata) { KexiGUIMessageHandler handler; m_prjSet = new KexiProjectSet(&handler); if (!m_prjSet->setConnectionData(conndata)) { handler.showErrorMessage(m_prjSet->result()); delete m_prjSet; m_prjSet = 0; return; } QVBoxLayout *vbox = new QVBoxLayout(m_srcDBPageWidget); KexiUtils::setStandardMarginsAndSpacing(vbox); m_srcDBName = new KexiProjectSelectorWidget(m_srcDBPageWidget, m_prjSet); vbox->addWidget(m_srcDBName); m_srcDBName->label()->setText(xi18n("Select source database you wish to import:")); } m_srcDBPageWidget->show(); } } void ImportTableWizard::arriveTableSelectPage(KPageWidgetItem *prevPage) { if (prevPage == m_alterTablePageItem) { if (m_tableListWidget->count() == 1) { //we was skiping it before back(); } } else { Kexi::ObjectStatus result; KexiUtils::WaitCursor wait; m_tableListWidget->clear(); m_migrateDriver = prepareImport(&result); bool ok = m_migrateDriver; if (ok) { ok = m_migrateDriver->connectSource(&result); } if (ok) { QStringList tableNames; if (m_migrateDriver->tableNames(&tableNames)) { m_tableListWidget->addItems(tableNames); } if (m_tableListWidget->item(0)) { m_tableListWidget->item(0)->setSelected(true); if (m_tableListWidget->count() == 1) { KexiUtils::removeWaitCursor(); next(); } } } KexiUtils::removeWaitCursor(); if (!ok) { QString errMessage =result.message.isEmpty() ? xi18n("Unknown error") : result.message; QString errDescription = result.description.isEmpty() ? errMessage : result.description; KMessageBox::error(this, errMessage, errDescription); setValid(m_tablesPageItem, false); } } } void ImportTableWizard::arriveAlterTablePage() { //! @todo handle errors if (m_tableListWidget->selectedItems().isEmpty()) return; //! @todo (js) support multiple tables? #if 0 foreach(QListWidgetItem *table, m_tableListWidget->selectedItems()) { m_importTableName = table->text(); } #else m_importTableName = m_tableListWidget->selectedItems().first()->text(); #endif QScopedPointer ts(new KDbTableSchema); if (!m_migrateDriver->readTableSchema(m_importTableName, ts.data())) { return; } setValid(m_alterTablePageItem, ts->fieldCount() > 0); if (isValid(m_alterTablePageItem)) { connect(m_alterSchemaWidget->nameWidget(), SIGNAL(textChanged()), this, SLOT(slotNameChanged()), Qt::UniqueConnection); } m_alterSchemaWidget->setTableSchema(ts.take()); if (!readFromTable()) { m_alterSchemaWidget->setTableSchema(nullptr); back(); KMessageBox::information(this, xi18nc("@info", "Could not import table %1. " "Select different table or cancel importing.", m_importTableName)); } } bool ImportTableWizard::readFromTable() { QSharedPointer tableResult = m_migrateDriver->readFromTable(m_importTableName); KDbTableSchema *newSchema = m_alterSchemaWidget->newSchema(); if (!tableResult || tableResult->lastResult().isError() || tableResult->fieldsCount() != newSchema->fieldCount()) { back(); KMessageBox::information(this, xi18nc("@info", "Could not import table %1. " "Select different table or cancel importing.", m_importTableName)); return false; } QScopedPointer> data(new QList); for (int i = 0; i < RECORDS_FOR_PREVIEW; ++i) { QSharedPointer record(tableResult->fetchRecordData()); if (!record) { if (tableResult->lastResult().isError()) { return false; } break; } data->append(record.data()); } if (data->isEmpty()) { back(); KMessageBox::information(this, xi18nc("@info", "No data has been found in table %1. " "Select different table or cancel importing.", m_importTableName)); return false; } m_alterSchemaWidget->model()->setRowCount(data->count()); m_alterSchemaWidget->setData(data.take()); return true; } void ImportTableWizard::arriveImportingPage() { m_importingPageWidget->hide(); #if 0 if (checkUserInput()) { //setNextEnabled(m_importingPageWidget, true); user2Button->setEnabled(true); } else { //setNextEnabled(m_importingPageWidget, false); user2Button->setEnabled(false); } #endif QString txt; txt = xi18nc("@info Table import wizard, final message", "All required information has now been gathered. " "Click Next button to start importing table %1." "Depending on size of the table this may take some time.", m_alterSchemaWidget->nameWidget()->nameText()); m_lblImportingTxt->setText(txt); //temp. hack for MS Access driver only //! @todo for other databases we will need KexiMigration::Connection //! and KexiMigration::Driver classes bool showOptions = false; if (fileBasedSrcSelected()) { Kexi::ObjectStatus result; KexiMigrate* sourceDriver = prepareImport(&result); if (sourceDriver) { showOptions = !result.error() && sourceDriver->propertyValue("source_database_has_nonunicode_encoding").toBool(); sourceDriver->setData(nullptr); } } if (showOptions) m_importOptionsButton->show(); else m_importOptionsButton->hide(); m_importingPageWidget->show(); setAppropriate(m_progressPageItem, true); } void ImportTableWizard::arriveProgressPage() { m_progressLbl->setText(xi18nc("@info", "Please wait while the table is imported.")); backButton()->setEnabled(false); nextButton()->setEnabled(false); connect(button(QDialogButtonBox::Cancel), &QPushButton::clicked, this, &ImportTableWizard::slotCancelClicked); QApplication::setOverrideCursor(Qt::BusyCursor); m_importComplete = doImport(); QApplication::restoreOverrideCursor(); disconnect(button(QDialogButtonBox::Cancel), &QPushButton::clicked, this, &ImportTableWizard::slotCancelClicked); next(); } void ImportTableWizard::arriveFinishPage() { if (m_importComplete) { m_finishPageItem->setHeader(xi18n("Success")); m_finishLbl->setText(xi18nc("@info", "Table %1 has been imported.", m_alterSchemaWidget->nameWidget()->nameText())); } else { m_finishPageItem->setHeader(xi18n("Failure")); m_finishLbl->setText(xi18n("An error occured.")); } m_migrateDriver->disconnectSource(); button(QDialogButtonBox::Cancel)->setEnabled(!m_importComplete); m_finishCheckBox->setVisible(m_importComplete); finishButton()->setEnabled(m_importComplete); nextButton()->setEnabled(m_importComplete); setAppropriate(m_progressPageItem, false); } bool ImportTableWizard::fileBasedSrcSelected() const { return m_srcConnSel->selectedConnectionType() == KexiConnectionSelectorWidget::FileBased; } KexiMigrate* ImportTableWizard::prepareImport(Kexi::ObjectStatus *result) { Q_ASSERT(result); // Find a source (migration) driver name QString sourceDriverId = driverIdForSelectedSource(); if (sourceDriverId.isEmpty()) { result->setStatus(xi18n("No appropriate migration driver found."), m_migrateManager.possibleProblemsMessage()); } // Get a source (migration) driver KexiMigrate* sourceDriver = 0; if (!result->error()) { sourceDriver = m_migrateManager.driver(sourceDriverId); if (!sourceDriver || m_migrateManager.result().isError()) { qDebug() << "Import migrate driver error..."; result->setStatus(m_migrateManager.resultable()); } } // Set up source (migration) data required for connection if (sourceDriver && !result->error()) { #if 0 // Setup progress feedback for the GUI if (sourceDriver->progressSupported()) { m_progressBar->updateGeometry(); disconnect(sourceDriver, SIGNAL(progressPercent(int)), this, SLOT(progressUpdated(int))); connect(sourceDriver, SIGNAL(progressPercent(int)), this, SLOT(progressUpdated(int))); progressUpdated(0); } #endif bool keepData = true; #if 0 if (m_importTypeStructureAndDataCheckBox->isChecked()) { qDebug() << "Structure and data selected"; keepData = true; } else if (m_importTypeStructureOnlyCheckBox->isChecked()) { qDebug() << "structure only selected"; keepData = false; } else { qDebug() << "Neither radio button is selected (not possible?) presume keep data"; keepData = true; } #endif KexiMigration::Data* md = new KexiMigration::Data(); if (fileBasedSrcSelected()) { KDbConnectionData* conn_data = new KDbConnectionData(); conn_data->setDatabaseName(m_srcConnSel->selectedFileName()); md->source = conn_data; md->sourceName.clear(); } else { md->source = m_srcConnSel->selectedConnectionData(); md->sourceName = m_srcDBName->selectedProjectData()->databaseName(); } md->setShouldCopyData(keepData); sourceDriver->setData(md); return sourceDriver; } return 0; } //=========================================================== // QString ImportTableWizard::driverIdForSelectedSource() { if (fileBasedSrcSelected()) { QMimeDatabase db; QMimeType mime = db.mimeTypeForFile(m_srcConnSel->selectedFileName()); if (!mime.isValid() || mime.name() == "application/octet-stream" || mime.name() == "text/plain") { //try by URL: mime = db.mimeTypeForFile(m_srcConnSel->selectedFileName()); } if (!mime.isValid()) { return QString(); } const QStringList ids(m_migrateManager.driverIdsForMimeType(mime.name())); //! @todo do we want to return first migrate driver for the mime type or allow to select it? return ids.isEmpty() ? QString() : ids.first(); } return m_srcConnSel->selectedConnectionData() ? m_srcConnSel->selectedConnectionData()->databaseName() : QString(); } bool ImportTableWizard::doImport() { KexiGUIMessageHandler msg; KexiProject* project = KexiMainWindowIface::global()->project(); if (!project) { msg.showErrorMessage(KDbMessageHandler::Error, xi18n("No project available.")); return false; } KexiPart::Part *part = Kexi::partManager().partForPluginId("org.kexi-project.table"); if (!part) { msg.showErrorMessage(Kexi::partManager().result()); return false; } KDbTableSchema* newSchema = m_alterSchemaWidget->newSchema(); if (!newSchema) { msg.showErrorMessage(KDbMessageHandler::Error, xi18n("No table was selected to import.")); return false; } newSchema->setName(m_alterSchemaWidget->nameWidget()->nameText()); newSchema->setCaption(m_alterSchemaWidget->nameWidget()->captionText()); KexiPart::Item* partItemForSavedTable = project->createPartItem(part->info(), newSchema->name()); if (!partItemForSavedTable) { msg.showErrorMessage(project->result()); return false; } //Create the table - if (!m_connection->createTable(newSchema, true)) { + if (!m_connection->createTable(newSchema, + KDbConnection::CreateTableOption::Default | KDbConnection::CreateTableOption::DropDestination)) + { msg.showErrorMessage(KDbMessageHandler::Error, xi18nc("@info", "Unable to create table %1.", newSchema->name())); return false; } m_alterSchemaWidget->takeTableSchema(); //m_connection takes ownership of the KDbTableSchema object //Import the data QApplication::setOverrideCursor(Qt::BusyCursor); QList row; unsigned int fieldCount = newSchema->fieldCount(); m_migrateDriver->moveFirst(); KDbTransactionGuard tg(m_connection); if (m_connection->result().isError()) { QApplication::restoreOverrideCursor(); return false; } do { for (unsigned int i = 0; i < fieldCount; ++i) { if (m_importWasCanceled) { return false; } if (i % 100 == 0) { QApplication::processEvents(); } row.append(m_migrateDriver->value(i)); } m_connection->insertRecord(newSchema, row); row.clear(); } while (m_migrateDriver->moveNext()); if (!tg.commit()) { QApplication::restoreOverrideCursor(); return false; } QApplication::restoreOverrideCursor(); //Done so save part and update gui partItemForSavedTable->setIdentifier(newSchema->id()); project->addStoredItem(part->info(), partItemForSavedTable); return true; } void ImportTableWizard::slotConnPageItemSelected(bool isSelected) { setValid(m_srcConnPageItem, isSelected); if (isSelected && fileBasedSrcSelected()) { next(); } } void ImportTableWizard::slotTableListWidgetSelectionChanged() { setValid(m_tablesPageItem, !m_tableListWidget->selectedItems().isEmpty()); } void ImportTableWizard::slotNameChanged() { setValid(m_alterTablePageItem, !m_alterSchemaWidget->nameWidget()->captionText().isEmpty()); } void ImportTableWizard::slotCancelClicked() { m_importWasCanceled = true; } diff --git a/src/plugins/importexport/csv/kexicsvimportdialog.cpp b/src/plugins/importexport/csv/kexicsvimportdialog.cpp index d3168e707..45e1ecf64 100644 --- a/src/plugins/importexport/csv/kexicsvimportdialog.cpp +++ b/src/plugins/importexport/csv/kexicsvimportdialog.cpp @@ -1,2191 +1,2194 @@ /* This file is part of the KDE project Copyright (C) 2005-2016 Jarosław Staniek Copyright (C) 2012 Oleg Kukharchuk This work is based on kspread/dialogs/kspread_dlg_csv.cc. Copyright (C) 2002-2003 Norbert Andres Copyright (C) 2002-2003 Ariya Hidayat Copyright (C) 2002 Laurent Montel Copyright (C) 1999 David Faure 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 "kexicsvimportdialog.h" #include "KexiCSVImportDialogModel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kexicsvwidgets.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 #include #include #include #include #include #include #include #include #include #include #define _IMPORT_ICON koIconNeededWithSubs("change to file_import or so", "file_import","table") //! @internal An item delegate for KexiCSVImportDialog's table view class KexiCSVImportDialogItemDelegate : public QStyledItemDelegate { Q_OBJECT public: KexiCSVImportDialogItemDelegate(QObject *parent = 0); virtual QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const; }; KexiCSVImportDialogItemDelegate::KexiCSVImportDialogItemDelegate(QObject *parent) : QStyledItemDelegate(parent) { } QWidget* KexiCSVImportDialogItemDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const { QStyleOptionViewItem newOption(option); QWidget *editor = QStyledItemDelegate::createEditor(parent, newOption, index); if (editor && index.row() == 0) { QFont f(editor->font()); f.setBold(true); editor->setFont(f); } return editor; } // -- //! @internal class KexiCSVImportStatic { public: KexiCSVImportStatic() : types(QVector() << KDbField::Text << KDbField::Integer << KDbField::Double << KDbField::Boolean << KDbField::Date << KDbField::Time << KDbField::DateTime) { typeNames.insert(KDbField::Text, KDbField::typeGroupName(KDbField::TextGroup)); typeNames.insert(KDbField::Integer, KDbField::typeGroupName(KDbField::IntegerGroup)); typeNames.insert(KDbField::Double, KDbField::typeGroupName(KDbField::FloatGroup)); typeNames.insert(KDbField::Boolean, KDbField::typeName(KDbField::Boolean)); typeNames.insert(KDbField::Date, KDbField::typeName(KDbField::Date)); typeNames.insert(KDbField::Time, KDbField::typeName(KDbField::Time)); typeNames.insert(KDbField::DateTime, KDbField::typeName(KDbField::DateTime)); for (int i = 0; i < types.size(); ++i) { indicesForTypes.insert(types[i], i); } } const QVector types; QHash typeNames; QHash indicesForTypes; }; Q_GLOBAL_STATIC(KexiCSVImportStatic, kexiCSVImportStatic) #define MAX_ROWS_TO_PREVIEW 100 //max 100 rows is reasonable #define MAX_BYTES_TO_PREVIEW 10240 //max 10KB is reasonable #define MAX_CHARS_TO_SCAN_WHILE_DETECTING_DELIMITER 4096 #define MINIMUM_YEAR_FOR_100_YEAR_SLIDING_WINDOW 1930 #define PROGRESS_STEP_MS (1000/5) // 5 updates per second static bool shouldSaveRow(int row, bool firstRowForFieldNames) { return row > (firstRowForFieldNames ? 1 : 0); } // -- class Q_DECL_HIDDEN KexiCSVImportDialog::Private { public: Private() : imported(false) { } ~Private() { qDeleteAll(m_uniquenessTest); } void clearDetectedTypes() { m_detectedTypes.clear(); } void clearUniquenessTests() { qDeleteAll(m_uniquenessTest); m_uniquenessTest.clear(); } KDbField::Type detectedType(int col) const { return m_detectedTypes.value(col, KDbField::InvalidType); } void setDetectedType(int col, KDbField::Type type) { if (m_detectedTypes.count() <= col) { for (int i = m_detectedTypes.count(); i < col; ++i) { // append missing bits m_detectedTypes.append(KDbField::InvalidType); } m_detectedTypes.append(type); } else { m_detectedTypes[col] = type; } } QList* uniquenessTest(int col) const { return m_uniquenessTest.value(col); } void setUniquenessTest(int col, QList* test) { if (m_uniquenessTest.count() <= col) { for (int i = m_uniquenessTest.count(); i < col; ++i) { // append missing bits m_uniquenessTest.append(0); } m_uniquenessTest.append(test); } else { m_uniquenessTest[col] = test; } } bool imported; private: //! vector of detected types //! @todo more types QList m_detectedTypes; //! m_detectedUniqueColumns[i]==true means that i-th column has unique values //! (only for numeric type) QList< QList* > m_uniquenessTest; }; // -- KexiCSVImportDialog::KexiCSVImportDialog(Mode mode, QWidget * parent) : KAssistantDialog(parent), m_parseComments(false), m_canceled(false), m_adjustRows(true), m_startline(0), m_textquote(QString(KEXICSV_DEFAULT_FILE_TEXT_QUOTE)[0]), m_commentSymbol(QString(KEXICSV_DEFAULT_COMMENT_START)[0]), m_mode(mode), m_columnsAdjusted(false), m_firstFillTableCall(true), m_blockUserEvents(false), m_primaryKeyColumn(-1), m_dialogCanceled(false), m_conn(0), m_fieldsListModel(0), m_destinationTableSchema(0), m_implicitPrimaryKeyAdded(false), m_allRowsLoadedInPreview(false), m_stoppedAt_MAX_BYTES_TO_PREVIEW(false), m_stringNo("no"), m_stringI18nNo(xi18n("no")), m_stringFalse("false"), m_stringI18nFalse(xi18n("false")), m_newTable(false), m_partItemForSavedTable(0), m_importInProgress(false), m_importCanceled(false), d(new Private) { setWindowTitle( mode == File ? xi18nc("@title:window", "Import CSV Data From File") : xi18nc("@title:window", "Paste CSV Data From Clipboard") ); setWindowIcon(_IMPORT_ICON); //! @todo use "Paste CSV Data From Clipboard" caption for mode==Clipboard setObjectName("KexiCSVImportDialog"); setSizeGripEnabled(true); KexiMainWindowIface::global()->setReasonableDialogSize(this); KGuiItem::assign(configureButton(), KStandardGuiItem::configure()); finishButton()->setEnabled(false); backButton()->setEnabled(false); KConfigGroup importExportGroup(KSharedConfig::openConfig()->group("ImportExport")); m_maximumRowsForPreview = importExportGroup.readEntry( "MaximumRowsForPreviewInImportDialog", MAX_ROWS_TO_PREVIEW); m_maximumBytesForPreview = importExportGroup.readEntry( "MaximumBytesForPreviewInImportDialog", MAX_BYTES_TO_PREVIEW); m_minimumYearFor100YearSlidingWindow = importExportGroup.readEntry( "MinimumYearFor100YearSlidingWindow", MINIMUM_YEAR_FOR_100_YEAR_SLIDING_WINDOW); m_pkIcon = KexiSmallIcon("database-key"); if (m_mode == File) { createFileOpenPage(); } else if (m_mode == Clipboard) { QString subtype("plain"); m_clipboardData = QApplication::clipboard()->text(subtype, QClipboard::Clipboard); /* debug for (int i=0;QApplication::clipboard()->data(QClipboard::Clipboard)->format(i);i++) qDebug() << i << ": " << QApplication::clipboard()->data(QClipboard::Clipboard)->format(i); */ } else { return; } m_file = 0; m_inputStream = 0; createOptionsPage(); createImportMethodPage(); createTableNamePage(); createImportPage(); /** @todo reuse Clipboard too! */ /*if ( m_mode == Clipboard ) { setWindowTitle( xi18n( "Inserting From Clipboard" ) ); QMimeSource * mime = QApplication::clipboard()->data(); if ( !mime ) { KMessageBox::information( this, xi18n("There is no data in the clipboard.") ); m_canceled = true; return; } if ( !mime->provides( "text/plain" ) ) { KMessageBox::information( this, xi18n("There is no usable data in the clipboard.") ); m_canceled = true; return; } m_fileArray = QByteArray(mime->encodedData( "text/plain" ) ); } else if ( mode == File ) {*/ m_dateRegExp = QRegularExpression("^(\\d{1,4})([/\\-\\.])(\\d{1,2})([/\\-\\.])(\\d{1,4})$"); m_timeRegExp1 = QRegularExpression("^(\\d{1,2}):(\\d{1,2}):(\\d{1,2})$"); m_timeRegExp2 = QRegularExpression("^(\\d{1,2}):(\\d{1,2})$"); m_fpNumberRegExp1 = QRegularExpression("^[\\-]{0,1}\\d*[,\\.]\\d+$"); // E notation, e.g. 0.1e2, 0.1e+2, 0.1e-2, 0.1E2, 0.1E+2, 0.1E-2 m_fpNumberRegExp2 = QRegularExpression("^[\\-]{0,1}\\d*[,\\.]\\d+[Ee][+-]{0,1}\\d+$"); m_loadingProgressDlg = 0; if (m_mode == Clipboard) { m_infoLbl->setIcon(koIconName("edit-paste")); } m_tableView->setSelectionMode(QAbstractItemView::SingleSelection); connect(m_formatCombo, SIGNAL(activated(int)), this, SLOT(formatChanged(int))); connect(m_delimiterWidget, SIGNAL(delimiterChanged(QString)), this, SLOT(delimiterChanged(QString))); connect(m_commentWidget, SIGNAL(commentSymbolChanged(QString)), this, SLOT(commentSymbolChanged(QString))); connect(m_startAtLineSpinBox, SIGNAL(valueChanged(int)), this, SLOT(startlineSelected(int))); connect(m_comboQuote, SIGNAL(activated(int)), this, SLOT(textquoteSelected(int))); connect(m_tableView->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SLOT(currentCellChanged(QModelIndex,QModelIndex))); connect(m_ignoreDuplicates, SIGNAL(stateChanged(int)), this, SLOT(ignoreDuplicatesChanged(int))); connect(m_1stRowForFieldNames, SIGNAL(stateChanged(int)), this, SLOT(slot1stRowForFieldNamesChanged(int))); connect(configureButton(), &QPushButton::clicked, this, &KexiCSVImportDialog::optionsButtonClicked); connect(this, SIGNAL(currentPageChanged(KPageWidgetItem*,KPageWidgetItem*)), this, SLOT(slotCurrentPageChanged(KPageWidgetItem*,KPageWidgetItem*))); KexiUtils::installRecursiveEventFilter(this, this); if ( m_mode == Clipboard ) initLater(); } KexiCSVImportDialog::~KexiCSVImportDialog() { delete m_file; delete m_inputStream; delete d; } void KexiCSVImportDialog::next() { KPageWidgetItem *curPage = currentPage(); if (curPage == m_openFilePage) { m_fname = m_openFileWidget->highlightedFile(); if (m_openFileWidget->checkSelectedFile()) { m_fname = m_openFileWidget->highlightedFile(); } else { return; } if (m_fname.isEmpty()) { KMessageBox::sorry(this, xi18nc("@info", "Select source filename.")); return; } if (!openData()) { return; } } else if (curPage == m_optionsPage) { const int numRows(m_table->rowCount()); if (numRows == 0) return; //impossible if (numRows == 1) { if (KMessageBox::No == KMessageBox::questionYesNo(this, xi18n("Data set contains no rows. Do you want to import empty table?"))) return; } } else if (curPage == m_tableNamePage) { KexiGUIMessageHandler msg; KexiProject *project = KexiMainWindowIface::global()->project(); if (!project) { msg.showErrorMessage(KDbMessageHandler::Error, xi18n("No project available.")); return; } m_conn = project->dbConnection(); if (!m_conn) { msg.showErrorMessage(KDbMessageHandler::Error, xi18n("No database connection available.")); return; } if (m_newTable) { m_partItemForSavedTable->setCaption(m_newTableWidget->captionText()); m_partItemForSavedTable->setName(m_newTableWidget->nameText()); KexiPart::Part *part = Kexi::partManager().partForPluginId("org.kexi-project.table"); KDbObject tmp; tristate res = (part && part->info()) ? m_conn->loadObjectData( project->typeIdForPluginId(part->info()->pluginId()), m_newTableWidget->nameText(), &tmp) : false; if (res == true) { KMessageBox::information(this, "

" + part->i18nMessage("Object %1 already exists.", 0) .subs(m_newTableWidget->nameText()).toString() + "

" + xi18n("Please choose other name.") + "

" ); return; } else if (res == false) { qFatal("Plugin org.kexi-project.table not found"); return; } } else { m_partItemForSavedTable = m_tablesList->selectedPartItem(); } } KAssistantDialog::next(); } void KexiCSVImportDialog::slotShowSchema(KexiPart::Item *item) { if (!item) { return; } nextButton()->setEnabled(true); KDbTableOrQuerySchema *tableOrQuery = new KDbTableOrQuerySchema( KexiMainWindowIface::global()->project()->dbConnection(), item->identifier() ); m_tableCaptionLabel->setText(tableOrQuery->captionOrName()); m_tableNameLabel->setText(tableOrQuery->name()); m_recordCountLabel->setText(QString::number(KDb::recordCount(tableOrQuery))); m_colCountLabel->setText(QString::number(tableOrQuery->fieldCount())); delete m_fieldsListModel; m_fieldsListModel = new KexiFieldListModel(m_fieldsListView, ShowDataTypes); m_fieldsListModel->setSchema(tableOrQuery); m_fieldsListView->setModel(m_fieldsListModel); m_fieldsListView->header()->resizeSections(QHeaderView::ResizeToContents); } void KexiCSVImportDialog::slotCurrentPageChanged(KPageWidgetItem *page, KPageWidgetItem *prev) { nextButton()->setEnabled(page == m_saveMethodPage ? false : true); finishButton()->setEnabled(page == m_importPage ? true : false); if (page == m_importPage) { KGuiItem::assign(finishButton(), KGuiItem(xi18nc("@action:button Import CSV", "&Import..."), _IMPORT_ICON)); } configureButton()->setEnabled(page == m_optionsPage); nextButton()->setEnabled(page == m_importPage ? false : true); backButton()->setEnabled(page == m_openFilePage ? false : true); if (page == m_saveMethodPage && prev == m_tableNamePage && m_partItemForSavedTable) { if (m_newTable) { KexiMainWindowIface::global()->project()->deleteUnstoredItem(m_partItemForSavedTable); } m_partItemForSavedTable = 0; } if(page == m_optionsPage){ if (m_mode == File) { m_loadingProgressDlg = new QProgressDialog(this); m_loadingProgressDlg->setObjectName("m_loadingProgressDlg"); m_loadingProgressDlg->setLabelText( xi18nc("@info", "Loading CSV Data from %1...", QDir::toNativeSeparators(m_fname))); m_loadingProgressDlg->setWindowTitle(xi18nc("@title:window", "Loading CSV Data")); m_loadingProgressDlg->setModal(true); m_loadingProgressDlg->setMaximum(m_maximumRowsForPreview); m_loadingProgressDlg->show(); } // delimiterChanged(detectedDelimiter); // this will cause fillTable() m_detectDelimiter = true; m_columnsAdjusted = false; fillTable(); delete m_loadingProgressDlg; m_loadingProgressDlg = 0; if (m_dialogCanceled) { // m_loadingProgressDlg->hide(); // m_loadingProgressDlg->close(); QTimer::singleShot(0, this, SLOT(reject())); return; } currentCellChanged(m_table->index(0,0), QModelIndex()); if (m_loadingProgressDlg) m_loadingProgressDlg->hide(); m_tableView->setFocus(); } else if (page == m_saveMethodPage) { m_newTableButton->setFocus(); } else if (page == m_tableNamePage) { if (m_newTable && !m_partItemForSavedTable) { KexiGUIMessageHandler msg; KexiProject *project = KexiMainWindowIface::global()->project(); //get suggested name based on the file name QString suggestedName; if (m_mode == File) { suggestedName = QUrl(m_fname).fileName(); //remove extension if (!suggestedName.isEmpty()) { const int idx = suggestedName.lastIndexOf('.'); if (idx != -1) { suggestedName = suggestedName.mid(0, idx).simplified(); } } } KexiPart::Part *part = Kexi::partManager().partForPluginId("org.kexi-project.table"); if (!part) { msg.showErrorMessage(Kexi::partManager().result()); return; } //-new part item m_partItemForSavedTable = project->createPartItem(part->info(), suggestedName); if (!m_partItemForSavedTable) { msg.showErrorMessage(project->result()); return; } m_newTableWidget->setCaptionText(m_partItemForSavedTable->caption()); m_newTableWidget->setNameText(m_partItemForSavedTable->name()); m_newTableWidget->captionLineEdit()->setFocus(); m_newTableWidget->captionLineEdit()->selectAll(); } else if (!m_newTable) { KexiPart::Item *i = m_tablesList->selectedPartItem(); if (!i) { nextButton()->setEnabled(false); } slotShowSchema(i); } } else if (page == m_importPage) { m_fromLabel->setFileName(m_fname); m_toLabel->setFileNameText(m_partItemForSavedTable->name()); m_importingProgressBar->hide(); m_importProgressLabel->hide(); } } void KexiCSVImportDialog::createFileOpenPage() { m_openFileWidget = new KexiFileWidget( QUrl("kfiledialog:///CSVImportExport"), //startDir KexiFileWidget::Custom | KexiFileWidget::Opening, this); m_openFileWidget->setObjectName("m_openFileWidget"); m_openFileWidget->setAdditionalFilters(csvMimeTypes().toSet()); m_openFileWidget->setDefaultExtension("csv"); connect(m_openFileWidget, SIGNAL(fileSelected(QUrl)), this, SLOT(next())); m_openFilePage = new KPageWidgetItem(m_openFileWidget, xi18n("Select Import Filename")); addPage(m_openFilePage); } void KexiCSVImportDialog::createOptionsPage() { QWidget *m_optionsWidget = new QWidget(this); QVBoxLayout *lyr = new QVBoxLayout(m_optionsWidget); m_infoLbl = new KexiCSVInfoLabel( m_mode == File ? xi18n("Preview of data from file:") : xi18n("Preview of data from clipboard"), m_optionsWidget, m_mode == File /*showFnameLine*/ ); lyr->addWidget(m_infoLbl); QWidget* page = new QFrame(m_optionsWidget); QGridLayout *glyr = new QGridLayout(page); lyr->addWidget(page); // Delimiter: comma, semicolon, tab, space, other m_delimiterWidget = new KexiCSVDelimiterWidget(true /*lineEditOnBottom*/, page); glyr->addWidget(m_delimiterWidget, 1, 0, 2, 1); QLabel *delimiterLabel = new QLabel(xi18n("Delimiter:"), page); delimiterLabel->setBuddy(m_delimiterWidget); delimiterLabel->setAlignment(Qt::AlignLeft | Qt::AlignBottom); glyr->addWidget(delimiterLabel, 0, 0, 1, 1); m_commentWidget = new KexiCSVCommentWidget(true, page); glyr->addWidget(m_commentWidget, 1, 4); QLabel *commentLabel = new QLabel(xi18n("Comment symbol:"), page); commentLabel->setBuddy(m_commentWidget); commentLabel->setAlignment(Qt::AlignLeft | Qt::AlignBottom); glyr->addWidget(commentLabel, 0, 4); // Format: number, text... //! @todo Object and Currency types m_formatCombo = new KComboBox(page); m_formatCombo->setObjectName("m_formatCombo"); for (int i = 0; i < kexiCSVImportStatic->types.size(); ++i) { m_formatCombo->addItem(kexiCSVImportStatic->typeNames.value(kexiCSVImportStatic->types[i])); } glyr->addWidget(m_formatCombo, 1, 1, 1, 1); m_formatLabel = new QLabel(page); m_formatLabel->setBuddy(m_formatCombo); m_formatLabel->setAlignment(Qt::AlignLeft | Qt::AlignBottom); glyr->addWidget(m_formatLabel, 0, 1); m_primaryKeyField = new QCheckBox(xi18n("Primary key"), page); m_primaryKeyField->setObjectName("m_primaryKeyField"); glyr->addWidget(m_primaryKeyField, 2, 1); connect(m_primaryKeyField, SIGNAL(toggled(bool)), this, SLOT(slotPrimaryKeyFieldToggled(bool))); m_comboQuote = new KexiCSVTextQuoteComboBox(page); glyr->addWidget(m_comboQuote, 1, 2); TextLabel2 = new QLabel(xi18n("Text quote:"), page); TextLabel2->setBuddy(m_comboQuote); TextLabel2->setObjectName("TextLabel2"); TextLabel2->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); TextLabel2->setAlignment(Qt::AlignLeft | Qt::AlignBottom); glyr->addWidget(TextLabel2, 0, 2); m_startAtLineSpinBox = new QSpinBox(page); m_startAtLineSpinBox->setObjectName("m_startAtLineSpinBox"); m_startAtLineSpinBox->setMinimum(1); m_startAtLineSpinBox->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); m_startAtLineSpinBox->setMinimumWidth( QFontMetrics(m_startAtLineSpinBox->font()).width("8888888")); glyr->addWidget(m_startAtLineSpinBox, 1, 3); m_startAtLineLabel = new QLabel(page); m_startAtLineLabel->setBuddy(m_startAtLineSpinBox); m_startAtLineLabel->setObjectName("m_startAtLineLabel"); m_startAtLineLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); m_startAtLineLabel->setAlignment(Qt::AlignLeft | Qt::AlignBottom); glyr->addWidget(m_startAtLineLabel, 0, 3); m_ignoreDuplicates = new QCheckBox(page); m_ignoreDuplicates->setObjectName("m_ignoreDuplicates"); m_ignoreDuplicates->setText(xi18n("Ignore duplicated delimiters")); glyr->addWidget(m_ignoreDuplicates, 2, 2, 1, 2); m_1stRowForFieldNames = new QCheckBox(page); m_1stRowForFieldNames->setObjectName("m_1stRowForFieldNames"); m_1stRowForFieldNames->setText(xi18n("First row contains column names")); glyr->addWidget(m_1stRowForFieldNames, 3, 2, 1, 2); QSpacerItem* spacer_2 = new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::Preferred); glyr->addItem(spacer_2, 0, 5, 4, 1); glyr->setColumnStretch(5, 2); m_tableView = new QTableView(m_optionsWidget); m_table = new KexiCSVImportDialogModel(m_tableView); m_table->setObjectName("m_table"); m_tableView->setModel(m_table); m_tableItemDelegate = new KexiCSVImportDialogItemDelegate(m_tableView); m_tableView->setItemDelegate(m_tableItemDelegate); lyr->addWidget(m_tableView); QSizePolicy spolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); spolicy.setHorizontalStretch(1); spolicy.setVerticalStretch(1); m_tableView->setSizePolicy(spolicy); m_optionsPage = new KPageWidgetItem(m_optionsWidget, xi18n("Import Options")); addPage(m_optionsPage); } void KexiCSVImportDialog::createImportMethodPage() { m_saveMethodWidget = new QWidget(this); QGridLayout *l = new QGridLayout(m_saveMethodWidget); m_newTableButton = new KexiCommandLinkButton(xi18nc("@action:button", "New table"), xi18nc("CSV import: data will be appended to a new table", "Data will be appended to a new table"), m_saveMethodWidget); m_newTableButton->setArrowVisible(true); m_existentTableButton = new KexiCommandLinkButton(xi18nc("@action:button", "Existing table"), xi18nc("CSV import: data will be appended to existing table", "Data will be appended to existing table"), m_saveMethodWidget); m_existentTableButton->setArrowVisible(true); l->addWidget(m_newTableButton, 0, 0, 1, 1); l->addWidget(m_existentTableButton, 1, 0, 1, 1); QSpacerItem *hSpacer = new QSpacerItem(200, 20, QSizePolicy::Preferred, QSizePolicy::Minimum); QSpacerItem *vSpacer = new QSpacerItem(20, 200, QSizePolicy::Minimum, QSizePolicy::Expanding); l->addItem(hSpacer, 1, 1, 1, 1); l->addItem(vSpacer, 2, 0, 1, 1); m_saveMethodPage = new KPageWidgetItem(m_saveMethodWidget, xi18n("Choose Method of Saving Imported Data")); addPage(m_saveMethodPage); connect(m_newTableButton, SIGNAL(clicked()), this, SLOT(slotCommandLinkClicked())); connect(m_existentTableButton, SIGNAL(clicked()), this, SLOT(slotCommandLinkClicked())); } void KexiCSVImportDialog::createTableNamePage() { m_tableNameWidget = new QStackedWidget(this); m_tableNameWidget->setObjectName("m_tableNameWidget"); QWidget *page1=new QWidget(m_tableNameWidget); m_newTableWidget = new KexiNameWidget(QString(), page1); m_newTableWidget->addNameSubvalidator(new KDbObjectNameValidator( KexiMainWindowIface::global()->project()->dbConnection()->driver())); QVBoxLayout *l=new QVBoxLayout(page1); l->addWidget(m_newTableWidget); l->addStretch(1); m_tableNameWidget->addWidget(page1); QSplitter *splitter = new QSplitter(m_tableNameWidget); QWidget *tablesListParentWidget = new QWidget; QVBoxLayout *tablesListParentWidgetLayout = new QVBoxLayout(tablesListParentWidget); tablesListParentWidgetLayout->setMargin(0); QLabel *tablesListLabel = new QLabel(xi18nc("@label", "Select existing table:")); tablesListParentWidgetLayout->addWidget(tablesListLabel); KexiProjectNavigator::Features tablesListFeatures = KexiProjectNavigator::DefaultFeatures; tablesListFeatures &= (~KexiProjectNavigator::AllowSingleClickForOpeningItems); tablesListFeatures &= (~KexiProjectNavigator::ClearSelectionAfterAction); tablesListFeatures |= KexiProjectNavigator::Borders; m_tablesList = new KexiProjectNavigator(tablesListParentWidget, tablesListFeatures); tablesListParentWidgetLayout->addWidget(m_tablesList, 1); tablesListLabel->setBuddy(m_tablesList); QString errorString; m_tablesList->setProject(KexiMainWindowIface::global()->project(), "org.kexi-project.table", &errorString, false); connect (m_tablesList, SIGNAL(openOrActivateItem(KexiPart::Item*,Kexi::ViewMode)), this, SLOT(next())); connect (m_tablesList, SIGNAL(selectionChanged(KexiPart::Item*)), this, SLOT(slotShowSchema(KexiPart::Item*))); splitter->addWidget(tablesListParentWidget); QWidget *tableDetailsWidget = new QWidget; QFormLayout *formLayout = new QFormLayout(tableDetailsWidget); formLayout->setContentsMargins(KexiUtils::marginHint(), 0, 0, 0); formLayout->addRow(new QLabel(xi18nc("@label Preview of selected table", "Table preview:"))); formLayout->addRow(xi18nc("@label", "Name:"), m_tableNameLabel = new QLabel(tableDetailsWidget)); formLayout->addRow(xi18nc("@label", "Caption:"), m_tableCaptionLabel = new QLabel(tableDetailsWidget)); formLayout->addRow(xi18nc("@label", "Row count:"), m_recordCountLabel = new QLabel(tableDetailsWidget)); formLayout->addRow(xi18nc("@label", "Column count:"), m_colCountLabel = new QLabel(tableDetailsWidget)); formLayout->addItem(new QSpacerItem(1, KexiUtils::spacingHint())); m_fieldsListView = new QTreeView(tableDetailsWidget); m_fieldsListView->setItemsExpandable(false); m_fieldsListView->setRootIsDecorated(false); QSizePolicy fieldsListViewPolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); fieldsListViewPolicy.setVerticalStretch(1); m_fieldsListView->setSizePolicy(fieldsListViewPolicy); formLayout->addRow(new QLabel(xi18nc("@label", "Fields:"))); formLayout->addRow(m_fieldsListView); splitter->addWidget(tableDetailsWidget); splitter->setStretchFactor(splitter->indexOf(tableDetailsWidget), 1); m_tableNameWidget->addWidget(splitter); m_tableNamePage = new KPageWidgetItem(m_tableNameWidget, xi18nc("@label", "Choose Name of Destination Table")); addPage(m_tableNamePage); } void KexiCSVImportDialog::createImportPage() { m_importWidget = new QWidget(this); m_fromLabel = new KexiCSVInfoLabel(m_mode == File ? xi18n("From CSV file:") : xi18n("From Clipboard"), m_importWidget, m_mode == File); m_fromLabel->separator()->hide(); if (m_mode != File) { m_fromLabel->setIcon(koIconName("edit-paste")); } m_toLabel = new KexiCSVInfoLabel(xi18nc("@label Importing CSV data to table:", "To table:"), m_importWidget, true); KexiPart::Info *partInfo = Kexi::partManager().infoForPluginId("org.kexi-project.table"); m_toLabel->setIcon(partInfo->iconName()); m_importProgressLabel = new QLabel(m_importWidget); m_importingProgressBar = new QProgressBar(m_importWidget); QVBoxLayout *l = new QVBoxLayout(m_importWidget); l->addWidget(m_fromLabel); l->addWidget(m_toLabel); l->addSpacing(m_importProgressLabel->fontMetrics().height()); l->addWidget(m_importProgressLabel); l->addWidget(m_importingProgressBar); l->addStretch(1); m_importingProgressBar->hide(); m_importProgressLabel->hide(); m_importPage = new KPageWidgetItem(m_importWidget, xi18n("Ready to Import")); addPage(m_importPage); } void KexiCSVImportDialog::slotCommandLinkClicked() { if (m_tableNameWidget) { m_newTable = (sender() == m_newTableButton ? true : false); m_tableNameWidget->setCurrentIndex(sender() == m_newTableButton ? 0 : 1); next(); } } void KexiCSVImportDialog::initLater() { if (!openData()) return; m_columnsAdjusted = false; fillTable(); delete m_loadingProgressDlg; m_loadingProgressDlg = 0; if (m_dialogCanceled) { QTimer::singleShot(0, this, SLOT(reject())); return; } currentCellChanged(m_table->index(0,0), QModelIndex()); if (m_loadingProgressDlg) m_loadingProgressDlg->hide(); show(); m_tableView->setFocus(); } bool KexiCSVImportDialog::openData() { if (m_mode != File) //data already loaded, no encoding stuff needed return true; delete m_inputStream; m_inputStream = 0; if (m_file) { m_file->close(); delete m_file; } m_file = new QFile(m_fname); if (!m_file->open(QIODevice::ReadOnly)) { m_file->close(); delete m_file; m_file = 0; KMessageBox::sorry(this, xi18n("Cannot open input file %1.", QDir::toNativeSeparators(m_fname))); nextButton()->setEnabled(false); m_canceled = true; if (parentWidget()) parentWidget()->raise(); return false; } return true; } bool KexiCSVImportDialog::canceled() const { return m_canceled; } void KexiCSVImportDialog::fillTable() { KexiUtils::WaitCursor wc(true); repaint(); m_blockUserEvents = true; button(QDialogButtonBox::Cancel)->setEnabled(true); KexiUtils::WaitCursor wait; if (m_table->rowCount() > 0) //to accept editor m_tableView->setCurrentIndex(QModelIndex()); int row, column, maxColumn; QString field; m_table->clear(); d->clearDetectedTypes(); d->clearUniquenessTests(); m_primaryKeyColumn = -1; if (true != loadRows(field, row, column, maxColumn, true)) return; // file with only one line without EOL if (field.length() > 0) { setText(row - m_startline, column, field, true); ++row; field.clear(); } adjustRows(row - m_startline - (m_1stRowForFieldNames->isChecked() ? 1 : 0)); maxColumn = qMax(maxColumn, column); m_table->setColumnCount(maxColumn); for (column = 0; column < m_table->columnCount(); ++column) { updateColumn(column); if (!m_columnsAdjusted) m_tableView->resizeColumnToContents(column); } m_columnsAdjusted = true; if (m_primaryKeyColumn >= 0 && m_primaryKeyColumn < m_table->columnCount()) { if (KDbField::Integer != d->detectedType(m_primaryKeyColumn)) { setPrimaryKeyIcon(m_primaryKeyColumn, false); m_primaryKeyColumn = -1; } } m_tableView->setCurrentIndex(m_table->index(0, 0)); currentCellChanged(m_table->index(0, 0), QModelIndex()); setPrimaryKeyIcon(m_primaryKeyColumn, true); const int count = qMax(0, m_table->rowCount() - 1 + m_startline); m_allRowsLoadedInPreview = count < m_maximumRowsForPreview && !m_stoppedAt_MAX_BYTES_TO_PREVIEW; if (count > 1) { if (m_allRowsLoadedInPreview) { m_startAtLineSpinBox->setMaximum(count); m_startAtLineSpinBox->setValue(m_startline + 1); } m_startAtLineSpinBox->setEnabled(true); m_startAtLineLabel->setText( m_allRowsLoadedInPreview ? xi18n("Start at line (1-%1):", count) : xi18n("Start at line:") //we do not know what's real count ); m_startAtLineLabel->setEnabled(true); } else { // no data m_startAtLineSpinBox->setMaximum(1); m_startAtLineSpinBox->setValue(1); m_startAtLineSpinBox->setEnabled(false); m_startAtLineLabel->setText(xi18n("Start at line:")); m_startAtLineLabel->setEnabled(false); } updateRowCountInfo(); m_blockUserEvents = false; repaint(); } QString KexiCSVImportDialog::detectDelimiterByLookingAtFirstBytesOfFile(QTextStream *inputStream) { // try to detect delimiter // \t has priority, then ; then , const qint64 origOffset = inputStream->pos(); QChar c, prevChar = 0; int detectedDelimiter = 0; bool insideQuote = false; //characters by priority const int CH_TAB_AFTER_QUOTE = 500; const int CH_SEMICOLON_AFTER_QUOTE = 499; const int CH_COMMA_AFTER_QUOTE = 498; const int CH_TAB = 200; // \t const int CH_SEMICOLON = 199; // ; const int CH_COMMA = 198; // , QList tabsPerLine, semicolonsPerLine, commasPerLine; int tabs = 0, semicolons = 0, commas = 0; int line = 0; bool wasChar13 = false; // true if previous x was '\r' for (int i = 0; !inputStream->atEnd() && i < MAX_CHARS_TO_SCAN_WHILE_DETECTING_DELIMITER; i++) { (*m_inputStream) >> c; // read one char if (prevChar == '"') { if (c != '"') //real quote (not double "") insideQuote = !insideQuote; } if (insideQuote) { prevChar = c; continue; } if (c == ' ') continue; if (wasChar13 && c == '\n') { wasChar13 = false; continue; // previous x was '\r', eat '\n' } wasChar13 = c == '\r'; if (c == '\n' || c == '\r') {//end of line //remember # of tabs/semicolons/commas in this line tabsPerLine += tabs; tabs = 0; semicolonsPerLine += semicolons; semicolons = 0; commasPerLine += commas; commas = 0; line++; } else if (c == '\t') { tabs++; detectedDelimiter = qMax(prevChar == '"' ? CH_TAB_AFTER_QUOTE : CH_TAB, detectedDelimiter); } else if (c == ';') { semicolons++; detectedDelimiter = qMax(prevChar == '"' ? CH_SEMICOLON_AFTER_QUOTE : CH_SEMICOLON, detectedDelimiter); } else if (c == ',') { commas++; detectedDelimiter = qMax(prevChar == '"' ? CH_COMMA_AFTER_QUOTE : CH_COMMA, detectedDelimiter); } prevChar = c; } inputStream->seek(origOffset); //restore orig. offset //now, try to find a delimiter character that exists the same number of times in all the checked lines //this detection method has priority over others QList::ConstIterator it; if (tabsPerLine.count() > 1) { tabs = tabsPerLine.isEmpty() ? 0 : tabsPerLine.first(); for (it = tabsPerLine.constBegin(); it != tabsPerLine.constEnd(); ++it) { if (tabs != *it) break; } if (tabs > 0 && it == tabsPerLine.constEnd()) return "\t"; } if (semicolonsPerLine.count() > 1) { semicolons = semicolonsPerLine.isEmpty() ? 0 : semicolonsPerLine.first(); for (it = semicolonsPerLine.constBegin(); it != semicolonsPerLine.constEnd(); ++it) { if (semicolons != *it) break; } if (semicolons > 0 && it == semicolonsPerLine.constEnd()) return ";"; } if (commasPerLine.count() > 1) { commas = commasPerLine.first(); for (it = commasPerLine.constBegin(); it != commasPerLine.constEnd(); ++it) { if (commas != *it) break; } if (commas > 0 && it == commasPerLine.constEnd()) return ","; } //now return the winning character by looking at CH_* symbol if (detectedDelimiter == CH_TAB_AFTER_QUOTE || detectedDelimiter == CH_TAB) return "\t"; if (detectedDelimiter == CH_SEMICOLON_AFTER_QUOTE || detectedDelimiter == CH_SEMICOLON) return ";"; if (detectedDelimiter == CH_COMMA_AFTER_QUOTE || detectedDelimiter == CH_COMMA) return ","; return KEXICSV_DEFAULT_FILE_DELIMITER; //<-- default } tristate KexiCSVImportDialog::loadRows(QString &field, int &row, int &column, int &maxColumn, bool inGUI) { enum { S_START, S_QUOTED_FIELD, S_MAYBE_END_OF_QUOTED_FIELD, S_END_OF_QUOTED_FIELD, S_MAYBE_NORMAL_FIELD, S_NORMAL_FIELD, S_COMMENT } state = S_START; field.clear(); const bool ignoreDups = m_ignoreDuplicates->isChecked(); bool lastCharDelimiter = false; bool nextRow = false; row = column = 1; m_prevColumnForSetText = 0; maxColumn = 0; QChar x; const bool hadInputStream = m_inputStream != 0; delete m_inputStream; if (m_mode == Clipboard) { m_inputStream = new QTextStream(&m_clipboardData, QIODevice::ReadOnly); if (!hadInputStream) m_delimiterWidget->setDelimiter(KEXICSV_DEFAULT_CLIPBOARD_DELIMITER); } else { m_file->seek(0); //always seek at 0 because loadRows() is called many times m_inputStream = new QTextStream(m_file); QTextCodec *codec = KCharsets::charsets()->codecForName(m_options.encoding); if (codec) { m_inputStream->setCodec(codec); //QTextCodec::codecForName("CP1250")); } if (m_detectDelimiter) { const QString delimiter(detectDelimiterByLookingAtFirstBytesOfFile(m_inputStream)); if (m_delimiterWidget->delimiter() != delimiter) m_delimiterWidget->setDelimiter(delimiter); } } const QChar delimiter(m_delimiterWidget->delimiter()[0]); const QChar commentSymbol(m_commentWidget->commentSymbol()[0]); m_stoppedAt_MAX_BYTES_TO_PREVIEW = false; if (m_importingProgressBar) { m_elapsedTimer.start(); m_elapsedMs = m_elapsedTimer.elapsed(); } int offset = 0; bool wasChar13 = false; // true if previous x was '\r' for (;; ++offset) { if (m_importingProgressBar && (offset % 0x100) == 0 && (m_elapsedMs + PROGRESS_STEP_MS) < m_elapsedTimer.elapsed()) { //update progr. bar dlg on final exporting m_elapsedMs = m_elapsedTimer.elapsed(); m_importingProgressBar->setValue(offset); qApp->processEvents(); if (m_importCanceled) { return ::cancelled; } } if (m_inputStream->atEnd()) { if (x != '\n' && x != '\r') { x = '\n'; // simulate missing \n at end wasChar13 = false; } else { break; // finish! } } else { (*m_inputStream) >> x; // read one char } if (wasChar13 && x == '\n') { wasChar13 = false; continue; // previous x was '\r', eat '\n' } wasChar13 = x == '\r'; if (offset == 0 && x.unicode() == 0xfeff) { // Ignore BOM, the "Byte Order Mark" // (http://en.wikipedia.org/wiki/Byte_Order_Mark, // http://www.unicode.org/charts/PDF/UFFF0.pdf) // Probably fixed in Qt4. continue; } switch (state) { case S_START : if (x == m_textquote) { state = S_QUOTED_FIELD; } else if (x == delimiter) { field.clear(); if ((ignoreDups == false) || (lastCharDelimiter == false)) ++column; lastCharDelimiter = true; } else if (x == '\n' || x == '\r' || (x == commentSymbol && m_parseComments)) { if (!inGUI) { //fill remaining empty fields (database wants them explicitly) for (int additionalColumn = column; additionalColumn <= maxColumn; additionalColumn++) { setText(row - m_startline, additionalColumn, QString(), inGUI); } } nextRow = true; if (ignoreDups && lastCharDelimiter) { // we're ignoring repeated delimiters so remove any extra trailing delimiters --column; } maxColumn = qMax(maxColumn, column); column = 1; m_prevColumnForSetText = 0; if (x == commentSymbol && m_parseComments) { state = S_COMMENT; maxColumn -= 1; break; } } else { field += x; state = S_MAYBE_NORMAL_FIELD; } break; case S_QUOTED_FIELD : if (x == m_textquote) { state = S_MAYBE_END_OF_QUOTED_FIELD; } /*allow \n inside quoted fields else if (x == '\n') { setText(row - m_startline, column, field, inGUI); field = ""; if (x == '\n') { nextRow = true; maxColumn = qMax( maxColumn, column ); column = 1; } else { if ((ignoreDups == false) || (lastCharDelimiter == false)) ++column; lastCharDelimiter = true; } state = S_START; }*/ else { field += x; } break; case S_MAYBE_END_OF_QUOTED_FIELD : if (x == m_textquote) { field += x; //no, this was just escaped quote character state = S_QUOTED_FIELD; } else if (x == delimiter || x == '\n' || x == '\r' || (x == commentSymbol && m_parseComments)) { setText(row - m_startline, column, field, inGUI); field.clear(); if (x == '\n' || x == '\r' || (x == commentSymbol && m_parseComments)) { nextRow = true; maxColumn = qMax(maxColumn, column); column = 1; m_prevColumnForSetText = 0; if (x == commentSymbol && m_parseComments) { state = S_COMMENT; break; } } else { if ((ignoreDups == false) || (lastCharDelimiter == false)) ++column; lastCharDelimiter = true; } state = S_START; } else { state = S_END_OF_QUOTED_FIELD; } break; case S_END_OF_QUOTED_FIELD : if (x == delimiter || x == '\n' || x == '\r' || (x == commentSymbol && m_parseComments)) { setText(row - m_startline, column, field, inGUI); field.clear(); if (x == '\n' || x == '\r' || (x == commentSymbol && m_parseComments)) { nextRow = true; maxColumn = qMax(maxColumn, column); column = 1; m_prevColumnForSetText = 0; if (x == commentSymbol && m_parseComments) { state = S_COMMENT; break; } } else { if ((ignoreDups == false) || (lastCharDelimiter == false)) ++column; lastCharDelimiter = true; } state = S_START; } else { state = S_END_OF_QUOTED_FIELD; } break; case S_COMMENT : if (x == '\n' || x == '\r') { state = S_START; } if (lastCharDelimiter) { lastCharDelimiter = false; } break; case S_MAYBE_NORMAL_FIELD : if (x == m_textquote) { field.clear(); state = S_QUOTED_FIELD; break; } case S_NORMAL_FIELD : if (x == delimiter || x == '\n' || x == '\r' || (x == commentSymbol && m_parseComments)) { setText(row - m_startline, column, field, inGUI); field.clear(); if (x == '\n' || x == '\r' || (x == commentSymbol && m_parseComments)) { nextRow = true; maxColumn = qMax(maxColumn, column); column = 1; m_prevColumnForSetText = 0; if (x == commentSymbol && m_parseComments) { state = S_COMMENT; break; } } else { if ((ignoreDups == false) || (lastCharDelimiter == false)) ++column; lastCharDelimiter = true; } state = S_START; } else { field += x; } } if (x != delimiter) lastCharDelimiter = false; if (nextRow) { if (!inGUI && !shouldSaveRow(row - m_startline, m_1stRowForFieldNames->isChecked())) { // do not save to the database 1st row if it contains column names m_valuesToInsert.clear(); } else if (!saveRow(inGUI)) return false; ++row; } if (m_firstFillTableCall && row == 2 && !m_1stRowForFieldNames->isChecked() && m_table->firstRowForFieldNames()) { m_table->clear(); m_firstFillTableCall = false; //this trick is allowed only once, on startup m_1stRowForFieldNames->setChecked(true); //this will reload table m_blockUserEvents = false; repaint(); return false; } if (!m_importingProgressBar && row % 20 == 0) { qApp->processEvents(); //only for GUI mode: if (!m_firstFillTableCall && m_loadingProgressDlg && m_loadingProgressDlg->wasCanceled()) { delete m_loadingProgressDlg; m_loadingProgressDlg = 0; m_dialogCanceled = true; reject(); return false; } } if (!m_firstFillTableCall && m_loadingProgressDlg) { m_loadingProgressDlg->setValue(qMin(m_maximumRowsForPreview, row)); } if (inGUI && row > (m_maximumRowsForPreview + (m_table->firstRowForFieldNames() ? 1 : 0))) { qDebug() << "loading stopped at row #" << m_maximumRowsForPreview; break; } if (nextRow) { nextRow = false; //additional speedup: stop processing now if too many bytes were loaded for preview qDebug() << offset; if (inGUI && offset >= m_maximumBytesForPreview && row >= 2) { m_stoppedAt_MAX_BYTES_TO_PREVIEW = true; return true; } } } return true; } void KexiCSVImportDialog::updateColumn(int col) { KDbField::Type detectedType = d->detectedType(col); if (detectedType == KDbField::InvalidType) { d->setDetectedType(col, KDbField::Text); //entirely empty column detectedType = KDbField::Text; } m_table->setHeaderData(col, Qt::Horizontal, QString(xi18n("Column %1", col + 1) + " \n(" + kexiCSVImportStatic->typeNames[detectedType].toLower() + ") ")); m_tableView->horizontalHeader()->adjustSize(); if (m_primaryKeyColumn == -1 && isPrimaryKeyAllowed(col)) { m_primaryKeyColumn = col; } } bool KexiCSVImportDialog::isPrimaryKeyAllowed(int col) { QList *list = d->uniquenessTest(col); if (m_primaryKeyColumn != -1 || !list || list->isEmpty()) { return false; } bool result = false; int expectedRowCount = m_table->rowCount(); if (m_table->firstRowForFieldNames()) { expectedRowCount--; } if (list->count() == expectedRowCount) { qSort(*list); QList::ConstIterator it = list->constBegin(); int prevValue = *it; ++it; for (; it != list->constEnd() && prevValue != (*it); ++it) { prevValue = (*it); } result = it == list->constEnd(); // no duplicates } list->clear(); // not needed now: conserve memory return result; } void KexiCSVImportDialog::detectTypeAndUniqueness(int row, int col, const QString& text) { int intValue; KDbField::Type type = d->detectedType(col); if (row == 1 || type != KDbField::Text) { bool found = false; if (text.isEmpty() && type == KDbField::InvalidType) found = true; //real type should be found later //detect type because it's 1st row or all prev. rows were not text //-FP number? (trying before "number" type is a must) if (!found && (row == 1 || type == KDbField::Integer || type == KDbField::Double || type == KDbField::InvalidType)) { bool ok = text.isEmpty() || m_fpNumberRegExp1.match(text).hasMatch() || m_fpNumberRegExp2.match(text).hasMatch(); if (ok && (row == 1 || type == KDbField::InvalidType)) { d->setDetectedType(col, KDbField::Double); found = true; //yes } } //-number? if (!found && (row == 1 || type == KDbField::Integer || type == KDbField::InvalidType)) { bool ok = text.isEmpty();//empty values allowed if (!ok) intValue = text.toInt(&ok); if (ok && (row == 1 || type == KDbField::InvalidType)) { d->setDetectedType(col, KDbField::Integer); found = true; //yes } } //-date? if (!found && (row == 1 || type == KDbField::Date || type == KDbField::InvalidType)) { if ((row == 1 || type == KDbField::InvalidType) && (text.isEmpty() || m_dateRegExp.match(text).hasMatch())) { d->setDetectedType(col, KDbField::Date); found = true; //yes } } //-time? if (!found && (row == 1 || type == KDbField::Time || type == KDbField::InvalidType)) { if ((row == 1 || type == KDbField::InvalidType) && (text.isEmpty() || m_timeRegExp1.match(text).hasMatch() || m_timeRegExp2.match(text).hasMatch())) { d->setDetectedType(col, KDbField::Time); found = true; //yes } } //-date/time? if (!found && (row == 1 || type == KDbField::Time || type == KDbField::InvalidType)) { if (row == 1 || type == KDbField::InvalidType) { bool detected = text.isEmpty(); if (!detected) { const QStringList dateTimeList(text.split(' ')); bool ok = dateTimeList.count() >= 2; //! @todo also support ISODateTime's "T" separator? //! @todo also support timezones? if (ok) { //try all combinations QString datePart(dateTimeList[0].trimmed()); QString timePart(dateTimeList[1].trimmed()); ok = m_dateRegExp.match(datePart).hasMatch() && (m_timeRegExp1.match(timePart).hasMatch() || m_timeRegExp2.match(timePart).hasMatch()); } detected = ok; } if (detected) { d->setDetectedType(col, KDbField::DateTime); found = true; //yes } } } if (!found && type == KDbField::InvalidType && !text.isEmpty()) { //eventually, a non-emptytext after a while d->setDetectedType(col, KDbField::Text); found = true; //yes } //default: text type (already set) } type = d->detectedType(col); //qDebug() << type; if (type == KDbField::Integer) { // check uniqueness for this value QList *list = d->uniquenessTest(col); if (text.isEmpty()) { if (list) { list->clear(); // empty value cannot be in PK } } else { if (!list) { list = new QList(); d->setUniquenessTest(col, list); } list->append(intValue); } } } QDate KexiCSVImportDialog::buildDate(int y, int m, int d) const { if (y < 100) { if ((1900 + y) >= m_minimumYearFor100YearSlidingWindow) return QDate(1900 + y, m, d); else return QDate(2000 + y, m, d); } return QDate(y, m, d); } bool KexiCSVImportDialog::parseDate(const QString& text, QDate& date) { QRegularExpressionMatch match = m_dateRegExp.match(text); if (!match.hasMatch()) return false; //dddd - dd - dddd //1 2 3 4 5 <- pos const int d1 = match.captured(1).toInt(), d3 = match.captured(3).toInt(), d5 = match.captured(5).toInt(); switch (m_options.dateFormat) { case KexiCSVImportOptions::DMY: date = buildDate(d5, d3, d1); break; case KexiCSVImportOptions::YMD: date = buildDate(d1, d3, d5); break; case KexiCSVImportOptions::MDY: date = buildDate(d5, d1, d3); break; case KexiCSVImportOptions::AutoDateFormat: if (match.captured(2) == "/") { //probably separator for american format mm/dd/yyyy date = buildDate(d5, d1, d3); } else { if (d5 > 31) //d5 == year date = buildDate(d5, d3, d1); else //d1 == year date = buildDate(d1, d3, d5); } break; default:; } return date.isValid(); } bool KexiCSVImportDialog::parseTime(const QString& text, QTime& time) { time = QTime::fromString(text, Qt::ISODate); //same as m_timeRegExp1 if (time.isValid()) return true; QRegularExpressionMatch match = m_timeRegExp2.match(text); if (match.hasMatch()) { //hh:mm:ss time = QTime(match.captured(1).toInt(), match.captured(3).toInt(), match.captured(5).toInt()); return true; } return false; } void KexiCSVImportDialog::setText(int row, int col, const QString& text, bool inGUI) { if (!inGUI) { if (!shouldSaveRow(row, m_1stRowForFieldNames->isChecked())) return; // do not care about this value if it contains column names (these were already used) //save text directly to database buffer if (m_prevColumnForSetText == 0) { //1st call m_valuesToInsert.clear(); if (m_implicitPrimaryKeyAdded) { m_valuesToInsert << QVariant(); //id will be autogenerated here } } if ((m_prevColumnForSetText + 1) < col) { //skipped one or more columns //before this: save NULLs first for (int i = m_prevColumnForSetText + 1; i < col; i++) { if (m_options.nullsImportedAsEmptyTextChecked && KDbField::isTextType(d->detectedType(i-1))) { m_valuesToInsert << QString(""); } else { m_valuesToInsert << QVariant(); } } } m_prevColumnForSetText = col; const KDbField::Type detectedType = d->detectedType(col-1); if (detectedType == KDbField::Integer) { m_valuesToInsert << (text.isEmpty() ? QVariant() : text.toInt()); //! @todo what about time and float/double types and different integer subtypes? } else if (detectedType == KDbField::Double) { //replace ',' with '.' QByteArray t(text.toLatin1()); const int textLen = t.length(); for (int i = 0; i < textLen; i++) { if (t[i] == ',') { t[i] = '.'; break; } } m_valuesToInsert << (t.isEmpty() ? QVariant() : t.toDouble()); } else if (detectedType == KDbField::Boolean) { const QString t(text.trimmed().toLower()); if (t.isEmpty()) m_valuesToInsert << QVariant(); else if (t == "0" || t == m_stringNo || t == m_stringI18nNo || t == m_stringFalse || t == m_stringI18nFalse) m_valuesToInsert << QVariant(false); else m_valuesToInsert << QVariant(true); //anything nonempty } else if (detectedType == KDbField::Date) { QDate date; if (parseDate(text, date)) m_valuesToInsert << date; else m_valuesToInsert << QVariant(); } else if (detectedType == KDbField::Time) { QTime time; if (parseTime(text, time)) m_valuesToInsert << time; else m_valuesToInsert << QVariant(); } else if (detectedType == KDbField::DateTime) { QStringList dateTimeList(text.split(' ')); if (dateTimeList.count() < 2) dateTimeList = text.split('T'); //also support ISODateTime's "T" separator //! @todo also support timezones? if (dateTimeList.count() >= 2) { //try all combinations QString datePart(dateTimeList[0].trimmed()); QDate date; if (parseDate(datePart, date)) { QString timePart(dateTimeList[1].trimmed()); QTime time; if (parseTime(timePart, time)) m_valuesToInsert << QDateTime(date, time); else m_valuesToInsert << QVariant(); } else m_valuesToInsert << QVariant(); } else m_valuesToInsert << QVariant(); } else { // Text type and the rest if (m_options.nullsImportedAsEmptyTextChecked && text.isNull()) { //default value is empty string not null - otherwise querying data without knowing SQL is very confusing m_valuesToInsert << QString(""); } else { m_valuesToInsert <columnCount() < col) { m_table->setColumnCount(col); } if (!m_1stRowForFieldNames->isChecked()) { if ((row + m_startline) == 1) {//this row is for column name if (m_table->firstRowForFieldNames() && !m_1stRowForFieldNames->isChecked()) { QString f(text.simplified()); if (f.isEmpty() || !f[0].isLetter()) { m_table->setFirstRowForFieldNames(false); } } } row++; //1st row was for column names } else { if ((row + m_startline) == 1) {//this is for column name m_table->setRowCount(1); QString colName(text.simplified()); if (!colName.isEmpty()) { if (colName.at(0) >= QLatin1Char('0') && colName.at(0) <= QLatin1Char('9')) { colName.prepend(xi18n("Column") + " "); } m_table->setData(m_table->index(0, col - 1), colName); } return; } } if (row < 2) // skipped by the user return; if (m_table->rowCount() < row) { m_table->setRowCount(row + 100); /* We add more rows at a time to limit recalculations */ m_adjustRows = true; } m_table->setData(m_table->index(row-1 ,col-1),m_options.trimmedInTextValuesChecked ? text.trimmed() : text); detectTypeAndUniqueness(row - 1, col - 1, text); } bool KexiCSVImportDialog::saveRow(bool inGUI) { if (inGUI) { //nothing to do return true; } bool res = m_importingStatement.execute(m_valuesToInsert); //! @todo move if (!res) { const QStringList msgList = KexiUtils::convertTypesUsingMethod(m_valuesToInsert); const KMessageBox::ButtonCode msgRes = KMessageBox::warningContinueCancelList(this, xi18nc("@info", "An error occurred during insert record."), QStringList(msgList.join(";")), QString(), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), "SkipImportErrors" ); res = msgRes == KMessageBox::Continue; } m_valuesToInsert.clear(); return res; } void KexiCSVImportDialog::adjustRows(int iRows) { if (m_adjustRows) { m_table->setRowCount(iRows); m_adjustRows = false; for (int i = 0; i < iRows; i++) m_tableView->resizeRowToContents(i); } } void KexiCSVImportDialog::formatChanged(int index) { if (index < 0 || index >= kexiCSVImportStatic->types.size()) return; KDbField::Type type = kexiCSVImportStatic->types[index]; d->setDetectedType(m_tableView->currentIndex().column(), type); m_primaryKeyField->setEnabled(KDbField::Integer == type); m_primaryKeyField->setChecked(m_primaryKeyColumn == m_tableView->currentIndex().column() && m_primaryKeyField->isEnabled()); updateColumn(m_tableView->currentIndex().column()); } void KexiCSVImportDialog::delimiterChanged(const QString& delimiter) { Q_UNUSED(delimiter); m_columnsAdjusted = false; m_detectDelimiter = false; //selected by hand: do not detect in the future //delayed, otherwise combobox won't be repainted fillTableLater(); } void KexiCSVImportDialog::commentSymbolChanged(const QString& commentSymbol) { QString noneString = QString(xi18n("None")); if (commentSymbol.compare(noneString) == 0) { m_parseComments = false; } else { m_parseComments = true; } m_columnsAdjusted = false; m_detectDelimiter = false; //selected by hand: do not detect in the future //delayed, otherwise combobox won't be repainted fillTableLater(); } void KexiCSVImportDialog::textquoteSelected(int) { const QString tq(m_comboQuote->textQuote()); if (tq.isEmpty()) m_textquote = 0; else m_textquote = tq[0]; qDebug() << m_textquote; //delayed, otherwise combobox won't be repainted fillTableLater(); } void KexiCSVImportDialog::fillTableLater() { m_table->setColumnCount(0); QTimer::singleShot(10, this, SLOT(fillTable())); } void KexiCSVImportDialog::startlineSelected(int startline) { if (m_startline == (startline - 1)) return; m_startline = startline - 1; m_adjustRows = true; m_columnsAdjusted = false; fillTable(); m_tableView->setFocus(); } void KexiCSVImportDialog::currentCellChanged(const QModelIndex &cur, const QModelIndex &prev) { if (prev.column() == cur.column() || !cur.isValid()) return; const KDbField::Type type = d->detectedType(cur.column()); m_formatCombo->setCurrentIndex(kexiCSVImportStatic->indicesForTypes.value(type, -1)); m_formatLabel->setText(xi18n("Format for column %1:", cur.column() + 1)); m_primaryKeyField->setEnabled(KDbField::Integer == type); m_primaryKeyField->blockSignals(true); //block to disable executing slotPrimaryKeyFieldToggled() m_primaryKeyField->setChecked(m_primaryKeyColumn == cur.column()); m_primaryKeyField->blockSignals(false); } //! Used in emergency by accept() void KexiCSVImportDialog::dropDestinationTable(KexiProject* project, KexiPart::Item* &partItemForSavedTable) { m_importingProgressBar->hide(); project->deleteUnstoredItem(partItemForSavedTable); partItemForSavedTable = 0; m_conn->dropTable(m_destinationTableSchema); /*alsoRemoveSchema*/ m_destinationTableSchema = 0; m_conn = 0; } //! Used in emergency by accept() void KexiCSVImportDialog::raiseErrorInAccept(KexiProject* project, KexiPart::Item* &partItemForSavedTable) { finishButton()->setEnabled(true); KGuiItem::assign(finishButton(), KGuiItem(xi18nc("@action:button Import CSV", "&Import..."), _IMPORT_ICON)); project->deleteUnstoredItem(partItemForSavedTable); partItemForSavedTable = 0; delete m_destinationTableSchema; m_destinationTableSchema = 0; m_conn = 0; backButton()->setEnabled(true); m_importInProgress = false; m_importingProgressBar->hide(); } void KexiCSVImportDialog::accept() { if (d->imported) { parentWidget()->raise(); bool openingCanceled; KexiWindow *win = KexiMainWindowIface::global()->openedWindowFor(m_partItemForSavedTable); if (win) { KexiMainWindowIface::global()->closeObject(m_partItemForSavedTable); } KexiMainWindowIface::global()->openObject(m_partItemForSavedTable, Kexi::DataViewMode, &openingCanceled); KAssistantDialog::accept(); } else { import(); } } void KexiCSVImportDialog::import() { //! @todo MOVE MOST OF THIS TO CORE/ (KexiProject?) after KexiWindow code is moved to non-gui place KMessageBox::enableMessage("SkipImportErrors"); KexiGUIMessageHandler msg; //! @todo make it better integrated with main window KexiProject *project = KexiMainWindowIface::global()->project(); if (!project) { msg.showErrorMessage(KDbMessageHandler::Error, xi18n("No project available.")); return; } m_conn = project->dbConnection(); if (!m_conn) { msg.showErrorMessage(KDbMessageHandler::Error, xi18n("No database connection available.")); return; } if (m_newTable) { m_destinationTableSchema = new KDbTableSchema(m_partItemForSavedTable->name()); m_destinationTableSchema->setCaption(m_partItemForSavedTable->caption()); m_destinationTableSchema->setDescription(m_partItemForSavedTable->description()); const int numCols(m_table->columnCount()); m_implicitPrimaryKeyAdded = false; //add PK if user wanted it int msgboxResult; if ( m_primaryKeyColumn == -1 && KMessageBox::No != (msgboxResult = KMessageBox::questionYesNoCancel(this, xi18nc("@info", "No primary key (autonumber) has been defined." "Should it be automatically defined on import (recommended)?" "An imported table without a primary key may not be " "editable (depending on database type)."), QString(), KGuiItem(xi18nc("@action:button Add Database Primary Key to a Table", "&Add Primary Key"), KexiIconName("database-key")), KGuiItem(xi18nc("@action:button Do Not Add Database Primary Key to a Table", "Do &Not Add"), KStandardGuiItem::no().icon())))) { if (msgboxResult == KMessageBox::Cancel) { raiseErrorInAccept(project, m_partItemForSavedTable); return; //cancel accepting } //add implicit PK field //! @todo make this field hidden (what about e.g. pgsql?) m_implicitPrimaryKeyAdded = true; QString fieldName("id"); QString fieldCaption("Id"); QSet colnames; for (int col = 0; col < numCols; col++) colnames.insert(m_table->data(m_table->index(0, col)).toString().toLower().simplified()); if (colnames.contains(fieldName)) { int num = 1; while (colnames.contains(fieldName + QString::number(num))) num++; fieldName += QString::number(num); fieldCaption += QString::number(num); } KDbField *field = new KDbField( fieldName, KDbField::Integer, KDbField::NoConstraints, KDbField::NoOptions, 0, 0, //int length=0, int precision=0, QVariant(), //QVariant defaultValue=QVariant(), fieldCaption ); //no description and width for now field->setPrimaryKey(true); field->setAutoIncrement(true); if (!m_destinationTableSchema->addField(field)) { msg.showErrorMessage(KDbMessageHandler::Error, xi18n("Cannot add column.")); delete field; delete m_destinationTableSchema; m_destinationTableSchema = 0; return; } } for (int col = 0; col < numCols; col++) { QString fieldCaption(m_table->data(m_table->index(0, col)).toString().simplified()); QString fieldName; if (fieldCaption.isEmpty()) { int i = 0; do { fieldCaption = xi18nc("@title:column Column 1, Column 2, etc.", "Column %1", i + 1); fieldName = KDb::stringToIdentifier(fieldCaption); if (!m_destinationTableSchema->field(fieldName)) { break; } i++; } while (true); } else { fieldName = KDb::stringToIdentifier(fieldCaption); if (m_destinationTableSchema->field(fieldName)) { QString fixedFieldName; int i = 2; //"apple 2, apple 3, etc. if there're many "apple" names do { fixedFieldName = fieldName + "_" + QString::number(i); if (!m_destinationTableSchema->field(fixedFieldName)) break; i++; } while (true); fieldName = fixedFieldName; fieldCaption += (" " + QString::number(i)); } } KDbField::Type detectedType = d->detectedType(col); //! @todo what about time and float/double types and different integer subtypes? //! @todo what about long text? if (detectedType == KDbField::InvalidType) { detectedType = KDbField::Text; } KDbField *field = new KDbField( fieldName, detectedType, KDbField::NoConstraints, KDbField::NoOptions, 0, 0, //int length=0, int precision=0, QVariant(), //QVariant defaultValue=QVariant(), fieldCaption ); //no description and width for now if ((int)col == m_primaryKeyColumn) { field->setPrimaryKey(true); field->setAutoIncrement(true); } if (!m_destinationTableSchema->addField(field)) { msg.showErrorMessage(KDbMessageHandler::Error, xi18n("Cannot add column.")); delete field; delete m_destinationTableSchema; m_destinationTableSchema = 0; return; } } } else { m_implicitPrimaryKeyAdded = false; m_destinationTableSchema = m_conn->tableSchema(m_partItemForSavedTable->name()); int firstColumn = 0; if (m_destinationTableSchema->field(0)->isPrimaryKey() && m_primaryKeyColumn == -1) { m_implicitPrimaryKeyAdded = true; firstColumn = 1; } if (m_destinationTableSchema->fields()->size() - firstColumn < m_table->columnCount()) { KMessageBox::error(this, xi18n("Field count does not match." "Please choose another table.")); return; } } m_importInProgress = true; backButton()->setEnabled(false); finishButton()->setEnabled(false); KexiPart::Part *part = Kexi::partManager().partForPluginId("org.kexi-project.table"); if (!part) { msg.showErrorMessage(Kexi::partManager().result()); return; } KDbTransaction transaction = m_conn->beginTransaction(); if (transaction.isNull()) { msg.showErrorMessage(m_conn->result()); raiseErrorInAccept(project, m_partItemForSavedTable); return; } KDbTransactionGuard tg(transaction); //-create physical table - if (m_newTable && !m_conn->createTable(m_destinationTableSchema, false /*allowOverwrite*/)) { + if (m_newTable && !m_conn->createTable(m_destinationTableSchema, + KDbConnection::CreateTableOptions(KDbConnection::CreateTableOption::Default) + & ~KDbConnection::CreateTableOptions(KDbConnection::CreateTableOption::DropDestination))) + { msg.showErrorMessage(m_conn->result()); raiseErrorInAccept(project, m_partItemForSavedTable); return; } m_importingStatement = m_conn->prepareStatement( KDbPreparedStatement::InsertStatement, m_destinationTableSchema); if (!m_importingStatement.isValid()) { msg.showErrorMessage(m_conn->result()); raiseErrorInAccept(project, m_partItemForSavedTable); return; } if (m_file) { m_importProgressLabel->setText(xi18n("Importing data...")); m_importingProgressBar->setMaximum(QFileInfo(*m_file).size() - 1); m_importingProgressBar->show(); m_importProgressLabel->show(); } int row, column, maxColumn; QString field; // main job tristate res = loadRows(field, row, column, maxColumn, false /*!gui*/); if (true != res) { //importing canceled or failed if (!res) { //do not display err msg when res == cancelled m_importProgressLabel->setText(xi18n("Import has been canceled.")); } else if (~res) { m_importProgressLabel->setText(xi18n("Error occurred during import.")); } raiseErrorInAccept(project, m_partItemForSavedTable); return; } // file with only one line without '\n' if (field.length() > 0) { setText(row - m_startline, column, field, false /*!gui*/); //fill remaining empty fields (database wants them explicitly) for (int additionalColumn = column; additionalColumn <= maxColumn; additionalColumn++) { setText(row - m_startline, additionalColumn, QString(), false /*!gui*/); } if (!saveRow(false /*!gui*/)) { msg.showErrorMessage(m_conn->result()); raiseErrorInAccept(project, m_partItemForSavedTable); return; } ++row; field.clear(); } if (!tg.commit()) { msg.showErrorMessage(m_conn->result()); raiseErrorInAccept(project, m_partItemForSavedTable); return; } //-now we can store the item if (m_newTable) { m_partItemForSavedTable->setIdentifier(m_destinationTableSchema->id()); project->addStoredItem(part->info(), m_partItemForSavedTable); } m_importingProgressBar->hide(); m_importProgressLabel->setText(xi18nc("@info", "Data has been successfully imported to table %1.", m_destinationTableSchema->name())); m_importInProgress = false; //qDebug()<<"IMPORT DONE"; KGuiItem::assign(finishButton(), KStandardGuiItem::open()); finishButton()->setEnabled(true); KGuiItem::assign(button(QDialogButtonBox::Cancel), KStandardGuiItem::close()); nextButton()->setEnabled(false); backButton()->setEnabled(false); m_conn = 0; d->imported = true; } void KexiCSVImportDialog::reject() { //qDebug()<<"IMP_P"<horizontalHeaderItem(col)->text(); if (header == xi18nc("Text type for column", "Text")) return TEXT; else if (header == xi18nc("Numeric type for column", "Number")) return NUMBER; else if (header == xi18nc("Currency type for column", "Currency")) return CURRENCY; else return DATE; } QString KexiCSVImportDialog::getText(int row, int col) { return m_table->item(row, col)->text(); } void KexiCSVImportDialog::ignoreDuplicatesChanged(int) { fillTable(); } void KexiCSVImportDialog::slot1stRowForFieldNamesChanged(int state) { m_adjustRows = true; if (m_1stRowForFieldNames->isChecked() && m_startline > 0 && m_startline >= (m_startAtLineSpinBox->maximum() - 1)) { m_startline--; } m_columnsAdjusted = false; fillTable(); m_table->setFirstRowForFieldNames(state); } void KexiCSVImportDialog::optionsButtonClicked() { KexiCSVImportOptionsDialog dlg(m_options, this); if (QDialog::Accepted != dlg.exec()) return; KexiCSVImportOptions newOptions(dlg.options()); if (m_options != newOptions) { m_options = newOptions; if (!openData()) return; fillTable(); } } bool KexiCSVImportDialog::eventFilter(QObject * watched, QEvent * e) { QEvent::Type t = e->type(); // temporary disable keyboard and mouse events for time-consuming tasks if (m_blockUserEvents && (t == QEvent::KeyPress || t == QEvent::KeyRelease || t == QEvent::MouseButtonPress || t == QEvent::MouseButtonDblClick || t == QEvent::Paint)) return true; if (watched == m_startAtLineSpinBox && t == QEvent::KeyPress) { QKeyEvent *ke = static_cast(e); if (ke->key() == Qt::Key_Enter || ke->key() == Qt::Key_Return) { m_tableView->setFocus(); return true; } } return QDialog::eventFilter(watched, e); } void KexiCSVImportDialog::slotPrimaryKeyFieldToggled(bool on) { setPrimaryKeyIcon(m_primaryKeyColumn, false); m_primaryKeyColumn = on ? m_tableView->currentIndex().column() : -1; setPrimaryKeyIcon(m_primaryKeyColumn, true); } void KexiCSVImportDialog::setPrimaryKeyIcon(int column, bool set) { if (column >= 0 && column < m_table->columnCount()) { m_table->setData(m_table->index(0, column), set ? m_pkIcon : QPixmap(), Qt::DecorationRole); } } void KexiCSVImportDialog::updateRowCountInfo() { m_infoLbl->setFileName(m_fname); if (m_allRowsLoadedInPreview) { m_infoLbl->setCommentText( xi18nc("row count", "(rows: %1)", m_table->rowCount() - 1 + m_startline)); m_infoLbl->commentLabel()->setToolTip(QString()); } else { m_infoLbl->setCommentText( xi18nc("row count", "(rows: more than %1)", m_table->rowCount() - 1 + m_startline)); m_infoLbl->commentLabel()->setToolTip(xi18n("Not all rows are visible on this preview")); } } QPushButton* KexiCSVImportDialog::configureButton() const { // Help button is used as Configure return button(QDialogButtonBox::Help); } #include "kexicsvimportdialog.moc" diff --git a/src/plugins/tables/kexitabledesignerview.cpp b/src/plugins/tables/kexitabledesignerview.cpp index 246d5f294..1130b3371 100644 --- a/src/plugins/tables/kexitabledesignerview.cpp +++ b/src/plugins/tables/kexitabledesignerview.cpp @@ -1,1890 +1,1894 @@ /* This file is part of the KDE project Copyright (C) 2004-2012 Jarosław 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 //! 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 // Defining this removes alter table! #define KEXI_NO_UNDOREDO_ALTERTABLE 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::ByteArray && toType == QVariant::String) || (fromType == QVariant::LongLong && toType == QVariant::ULongLong) || ((fromType == QVariant::String || fromType == QVariant::ByteArray) && (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.convert(toType)) return res; } return QVariant(); } KexiTableDesignerView::KexiTableDesignerView(QWidget *parent) : KexiDataTableView(parent, false/*not db-aware*/) , KexiTableDesignerInterface() , d(new KexiTableDesignerViewPrivate(this)) { setObjectName("KexiTableDesignerView"); KDbConnection *conn = KexiMainWindowIface::global()->project()->dbConnection(); d->view = dynamic_cast(mainWidget()); d->data = new KDbTableViewData(); if (conn->options()->isReadOnly()) d->data->setReadOnly(true); d->data->setInsertingEnabled(false); KDbTableViewColumn *col = new KDbTableViewColumn("pk", KDbField::Text, QString(), xi18n("Additional information about the field")); col->setIcon(KexiUtils::colorizeIconToTextColor(koSmallIcon("help-about"), d->view->palette())); col->setHeaderTextVisible(false); col->field()->setSubType("QIcon"); col->setReadOnly(true); d->data->addColumn(col); col = new KDbTableViewColumn("caption", KDbField::Text, xi18n("Field Caption"), xi18n("Describes caption for the field")); d->data->addColumn(col); col = new KDbTableViewColumn("type", KDbField::Enum, xi18n("Data Type"), xi18n("Describes data type for the field")); d->data->addColumn(col); #ifdef KEXI_NO_BLOB_FIELDS //! @todo remove this later QVector types(KDbField::LastTypeGroup - 1); //don't show last type (BLOB) #else QVector types(KDbField::LastTypeGroup); #endif d->maxTypeNameTextWidth = 0; QFontMetrics fm(font()); for (int i = KDbField::TextGroup; i <= types.count(); i++) { types[i-1] = KDbField::typeGroupName(KDb::intToFieldTypeGroup(i)); d->maxTypeNameTextWidth = qMax(d->maxTypeNameTextWidth, fm.width(types[i-1])); } col->field()->setEnumHints(types); d->data->addColumn(col = new KDbTableViewColumn("comments", KDbField::Text, xi18n("Comments"), xi18n("Describes additional comments for the field"))); d->view->setSpreadSheetMode(true); connect(d->data, SIGNAL(aboutToChangeCell(KDbRecordData*,int,QVariant*,KDbResultInfo*)), this, SLOT(slotBeforeCellChanged(KDbRecordData*,int,QVariant*,KDbResultInfo*))); connect(d->data, SIGNAL(recordUpdated(KDbRecordData*)), this, SLOT(slotRecordUpdated(KDbRecordData*))); connect(d->data, SIGNAL(aboutToDeleteRecord(KDbRecordData*,KDbResultInfo*,bool)), this, SLOT(slotAboutToDeleteRecord(KDbRecordData*,KDbResultInfo*,bool))); setMinimumSize(d->view->minimumSizeHint().width(), d->view->minimumSizeHint().height()); d->view->setFocus(); d->sets = new KexiDataAwarePropertySet(this, d->view); connect(d->sets, SIGNAL(recordDeleted()), this, SLOT(updateActions())); connect(d->sets, SIGNAL(recordInserted()), this, SLOT(slotRecordInserted())); connect(d->view->contextMenu(), SIGNAL(aboutToShow()), this, SLOT(slotAboutToShowContextMenu())); // - setup local actions QList viewActions; QAction* a; viewActions << (d->action_toggle_pkey = new KToggleAction(KexiIcon("database-key"), xi18n("Primary Key"), this)); a = d->action_toggle_pkey; a->setObjectName("tablepart_toggle_pkey"); a->setToolTip(xi18n("Sets or removes primary key")); a->setWhatsThis(xi18n("Sets or removes primary key for currently selected field.")); connect(a, SIGNAL(triggered()), this, SLOT(slotTogglePrimaryKey())); setViewActions(viewActions); d->view->contextMenu()->insertAction( d->view->contextMenu()->actions()[1], d->action_toggle_pkey); //add at the beginning as 2nd d->view->contextMenu()->insertSeparator(d->view->contextMenu()->actions()[2]); //as 3rd setAvailable("tablepart_toggle_pkey", !conn->options()->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); #endif #ifdef KEXI_DEBUG_GUI KDb::alterTableActionDebugGUI(QString()); //to create the tab KexiUtils::connectPushButtonActionForDebugWindow( "simulateAlterTableExecution", this, SLOT(slotSimulateAlterTableExecution())); KexiUtils::connectPushButtonActionForDebugWindow( "executeRealAlterTable", this, SLOT(executeRealAlterTable())); #endif // setup main menu actions QList mainMenuActions; a = sharedAction("edit_clear_table"); mainMenuActions << a; setMainMenuActions(mainMenuActions); } KexiTableDesignerView::~KexiTableDesignerView() { delete d; } void KexiTableDesignerView::initData() { //add column data d->data->deleteAllRecords(); int tableFieldCount = 0; d->primaryKeyExists = false; if (tempData()->table()) { tableFieldCount = tempData()->table()->fieldCount(); //recreate table data records for (int i = 0; i < tableFieldCount; i++) { KDbField *field = tempData()->table()->field(i); KDbRecordData *data = d->data->createItem(); if (field->isPrimaryKey()) { (*data)[COLUMN_ID_ICON] = KexiIconName("database-key"); d->primaryKeyExists = true; } else { KDbLookupFieldSchema *lookupFieldSchema = field->table() ? field->table()->lookupFieldSchema(*field) : 0; if (lookupFieldSchema && lookupFieldSchema->recordSource().type() != KDbLookupFieldSchemaRecordSource::NoType && !lookupFieldSchema->recordSource().name().isEmpty()) { (*data)[COLUMN_ID_ICON] = KexiIconName("combobox"); } } (*data)[COLUMN_ID_CAPTION] = field->captionOrName(); (*data)[COLUMN_ID_TYPE] = field->typeGroup() - 1; //-1 because type groups are counted from 1 (*data)[COLUMN_ID_DESC] = field->description(); d->data->append(data); } } //add empty space, at least 2 times more than number of existing fields int fullSize = qMax(d->sets->size(), 2 * tableFieldCount); for (int i = tableFieldCount; i < fullSize; i++) { 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++) { KDbField *field = tempData()->table()->field(i); createPropertySet(i, *field); } } //column widths d->view->setColumnWidth(COLUMN_ID_ICON, IconSize(KIconLoader::Small) + 10); d->view->setColumnResizeEnabled(COLUMN_ID_ICON, false); d->view->adjustColumnWidthToContents(COLUMN_ID_CAPTION); //adjust column width d->view->setColumnWidth(COLUMN_ID_TYPE, d->maxTypeNameTextWidth + 2 * d->view->recordHeight()); d->view->setStretchLastColumn(true); 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(KDbField::TypeGroup fieldTypeGroup, QStringList& stringsList, QStringList& namesList) { /* disabled - "mime" is moved from subType to "objectType" custom property if (fieldTypeGroup==KDbField::BLOBGroup) { // special case: BLOB type uses "mime-based" subtypes //! @todo hardcoded! stringsList << "image"; namesList << xi18n("Image object type", "Image"); } else {*/ stringsList = KDb::fieldTypeStringsForGroup(fieldTypeGroup); namesList = KDb::fieldTypeNamesForGroup(fieldTypeGroup); // } qDebug() << "subType strings: " << stringsList.join("|") << "\nnames: " << namesList.join("|"); } KPropertySet * KexiTableDesignerView::createPropertySet(int record, const KDbField& field, bool newOne) { KPropertySet *set = new KPropertySet(d->sets); if (KexiMainWindowIface::global()->project()->dbConnection()->options()->isReadOnly()) set->setReadOnly(true); KProperty *prop; set->addProperty(prop = new KProperty("uid", d->generateUniqueId(), "")); prop->setVisible(false); //meta-info for property editor set->addProperty(prop = new KProperty("this:classString", xi18nc("Table field", "Field"))); prop->setVisible(false); set->addProperty(prop = new KProperty("this:iconName", //! \todo add table_field icon KexiIconName("lineedit") //"table_field" )); prop->setVisible(false); set->addProperty(prop = new KProperty("this:useCaptionAsObjectName", QVariant(true), QString())); //we want "caption" to be displayed in the header, not name prop->setVisible(false); //name set->addProperty(prop = new KProperty( "name", QVariant(field.name()), xi18n("Name"), QString(), KexiCustomPropertyFactory::Identifier)); //type set->addProperty(prop = new KProperty("type", QVariant(field.type()), xi18n("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()==KDbField::BLOBGroup) { // special case: BLOB type uses "mime-based" subtypes //! @todo this should be retrieved from KDbField when BLOB supports many different mimetypes subTypeValue = slist.first(); } else {*/ QString subTypeValue = field.typeString(); //} set->addProperty(prop = new KProperty("subType", typeStringList, typeNameList, subTypeValue, xi18n("Subtype"))); // objectType QStringList objectTypeStringList, objectTypeNameList; //! @todo this should be retrieved from KDbField when BLOB supports many different mimetypes objectTypeStringList << "image"; objectTypeNameList << xi18nc("Image object type", "Image"); QString objectTypeValue(field.customProperty("objectType").toString()); if (objectTypeValue.isEmpty()) objectTypeValue = DEFAULT_OBJECT_TYPE_VALUE; set->addProperty(prop = new KProperty("objectType", objectTypeStringList, objectTypeNameList, objectTypeValue, xi18n("Subtype")/*! @todo other i18n string?*/)); set->addProperty(prop = new KProperty("caption", QVariant(field.caption()), xi18n("Caption"))); prop->setVisible(false);//always hidden set->addProperty(prop = new KProperty("description", QVariant(field.description()))); prop->setVisible(false);//always hidden set->addProperty(prop = new KProperty("unsigned", QVariant(field.isUnsigned()), xi18n("Unsigned Number"))); set->addProperty(prop = new KProperty("maxLength", field.maxLength(), xi18n("Max Length"))); set->addProperty(prop = new KProperty("maxLengthIsDefault", field.maxLengthStrategy() == KDbField::DefaultMaxLength)); prop->setVisible(false); //always hidden set->addProperty(prop = new KProperty("precision", (int)field.precision()/*200?*/, xi18n("Precision"))); #ifndef KEXI_SHOW_UNFINISHED prop->setVisible(false); #endif set->addProperty(prop = new KProperty("visibleDecimalPlaces", field.visibleDecimalPlaces(), xi18n("Visible Decimal Places"))); prop->setOption("min", -1); prop->setOption("minValueText", xi18nc("Auto Decimal Places", "Auto")); //! @todo set reasonable default for column width set->addProperty(prop = new KProperty("defaultWidth", QVariant(0) /*field.width()*//*200?*/, xi18n("Default Width"))); #ifndef KEXI_SHOW_UNFINISHED prop->setVisible(false); #endif set->addProperty(prop = new KProperty("defaultValue", field.defaultValue(), xi18n("Default Value"), QString(), //! @todo use "Variant" type here when supported by KProperty (KProperty::Type)field.variantType())); prop->setOption("3rdState", xi18n("None")); set->addProperty(prop = new KProperty("primaryKey", QVariant(field.isPrimaryKey()), xi18n("Primary Key"))); prop->setIconName(KexiIconName("database-key")); set->addProperty(prop = new KProperty("unique", QVariant(field.isUniqueKey()), xi18n("Unique"))); set->addProperty(prop = new KProperty("notNull", QVariant(field.isNotNull()), xi18n("Required"))); set->addProperty(prop = new KProperty("allowEmpty", QVariant(!field.isNotEmpty()), xi18n("Allow Zero\nSize"))); set->addProperty(prop = new KProperty("autoIncrement", QVariant(field.isAutoIncrement()), xi18n("Autonumber"))); prop->setIconName(koIconName("autonumber")); set->addProperty(prop = new KProperty("indexed", QVariant(field.isIndexed()), xi18n("Indexed"))); //- properties related to lookup columns (used and set by the "lookup column" // tab in the property pane) KDbLookupFieldSchema *lookupFieldSchema = field.table() ? field.table()->lookupFieldSchema(field) : 0; set->addProperty(prop = new KProperty("rowSource", lookupFieldSchema ? lookupFieldSchema->recordSource().name() : QString(), xi18n("Record Source"))); prop->setVisible(false); set->addProperty(prop = new KProperty("rowSourceType", lookupFieldSchema ? lookupFieldSchema->recordSource().typeName() : QString(), xi18nc("Record source type (in two records)", "Record Source\nType"))); prop->setVisible(false); set->addProperty(prop = new KProperty("boundColumn", lookupFieldSchema ? lookupFieldSchema->boundColumn() : -1, xi18n("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 kproperty editor needed int visibleColumn = -1; if (lookupFieldSchema && !lookupFieldSchema->visibleColumns().isEmpty()) visibleColumn = lookupFieldSchema->visibleColumns().first(); set->addProperty(prop = new KProperty("visibleColumn", visibleColumn, xi18n("Visible Column"))); prop->setVisible(false); //! @todo support columnWidths(), columnHeadersVisible(), maxVisibleRecords(), limitToList(), displayWidget() //---- d->updatePropertiesVisibility(field.type(), *set); connect(set, SIGNAL(propertyChanged(KPropertySet&,KProperty&)), this, SLOT(slotPropertyChanged(KPropertySet&,KProperty&))); d->sets->set(record, 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 && !KexiMainWindowIface::global()->project()->dbConnection()->options()->isReadOnly()); if (!propertySet()) return; KPropertySet &set = *propertySet(); d->slotTogglePrimaryKeyCalled = true; d->action_toggle_pkey->setChecked(set["primaryKey"].value().toBool()); d->slotTogglePrimaryKeyCalled = false; } void KexiTableDesignerView::slotUpdateRecordActions(int record) { KexiDataTableView::slotUpdateRecordActions(record); updateActions(); } void KexiTableDesignerView::slotTogglePrimaryKey() { if (d->slotTogglePrimaryKeyCalled) return; d->slotTogglePrimaryKeyCalled = true; if (!propertySet()) return; KPropertySet &set = *propertySet(); bool isSet = !set["primaryKey"].value().toBool(); set.changeProperty("primaryKey", QVariant(isSet)); //this will update all related properties as well d->slotTogglePrimaryKeyCalled = false; } void KexiTableDesignerView::switchPrimaryKey(KPropertySet &propertySet, bool set, bool aWasPKey, Command* commandGroup) { const bool was_pkey = aWasPKey || propertySet["primaryKey"].value().toBool(); d->setPropertyValueIfNeeded(propertySet, "primaryKey", QVariant(set), 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->selectedRecord()) { //show key in the table d->view->data()->clearRecordEditBuffer(); d->view->data()->updateRecordEditBuffer(d->view->selectedRecord(), COLUMN_ID_ICON, QVariant(set ? KexiIconName("database-key") : QLatin1String(""))); d->view->data()->saveRecordChanges(d->view->selectedRecord(), 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 KPropertySet *s = 0; int i; const int count = (int)d->sets->size(); for (i = 0; i < count; i++) { s = d->sets->at(i); if ( s && s != &propertySet && (*s)["primaryKey"].value().toBool() && i != d->view->currentRecord()) { break; } } if (i < count) {//remove d->setPropertyValueIfNeeded(*s, "autoIncrement", QVariant(false), commandGroup); d->setPropertyValueIfNeeded(*s, "primaryKey", QVariant(false), commandGroup); //remove key from table d->view->data()->clearRecordEditBuffer(); KDbRecordData *data = d->view->recordAt(i); if (data) { d->view->data()->updateRecordEditBuffer(data, COLUMN_ID_ICON, QVariant()); d->view->data()->saveRecordChanges(data, true); } } //set unsigned big-integer type d->slotBeforeCellChanged_enabled = false; d->view->data()->clearRecordEditBuffer(); d->view->data()->updateRecordEditBuffer(d->view->selectedRecord(), COLUMN_ID_TYPE, QVariant(KDbField::IntegerGroup - 1/*counting from 0*/)); d->view->data()->saveRecordChanges(d->view->selectedRecord(), true); d->setPropertyValueIfNeeded(propertySet, "subType", KDbField::typeString(KDbField::BigInteger), commandGroup); d->setPropertyValueIfNeeded(propertySet, "unsigned", QVariant(true), commandGroup); //! @todo d->slotBeforeCellChanged_enabled = true; } updateActions(); } tristate KexiTableDesignerView::beforeSwitchTo(Kexi::ViewMode mode, bool *dontStore) { Q_ASSERT(dontStore); if (!d->view->acceptRecordEditing()) return false; tristate res = true; if (mode == Kexi::DataViewMode) { if (!isDirty() && window()->neverSaved()) { KMessageBox::sorry(this, xi18n("Cannot switch to data view, because table design is empty.\n" "First, please create your design.")); return cancelled; } // else if (isDirty() && !window()->neverSaved()) { // cancelled = (KMessageBox::No == KMessageBox::questionYesNo(this, xi18n("Saving changes for existing table design is not yet supported.\nDo you want to discard your changes now?"))); // KDbConnection *conn = KexiMainWindowIface::global()->project()->dbConnection(); bool emptyTable; bool isPhysicalAlteringNeeded = this->isPhysicalAlteringNeeded(); KLocalizedString message( kxi18nc("@info", "Saving changes for existing table design is now required.%1") .subs(d->messageForSavingChanges(&emptyTable, /*skip warning?*/!isPhysicalAlteringNeeded))); if (emptyTable) { isPhysicalAlteringNeeded = false; // eventually, not needed because there's no data } KGuiItem saveItem(KStandardGuiItem::save()); saveItem.setToolTip(QString()); KGuiItem discardItem(KStandardGuiItem::discard()); discardItem.setToolTip(QString()); if (isPhysicalAlteringNeeded) { saveItem.setText(xi18nc("@action:button", "Save Design and Remove Table Data")); discardItem.setText(xi18nc("@action:button", "Discard Design")); } const KMessageBox::ButtonCode r = KMessageBox::warningYesNoCancel(this, message.toString(), QString(), saveItem, discardItem, KStandardGuiItem::cancel(), QString(), KMessageBox::Notify | KMessageBox::Dangerous); if (r == KMessageBox::Cancel) res = cancelled; else res = true; *dontStore = (r != KMessageBox::Yes); if (!*dontStore) d->dontAskOnStoreData = true; } // //! @todo return res; } else if (mode == Kexi::TextViewMode) { //! @todo } return res; } tristate KexiTableDesignerView::afterSwitchFrom(Kexi::ViewMode mode) { if (mode == Kexi::NoViewMode || mode == Kexi::DataViewMode) { initData(); } return true; } KPropertySet *KexiTableDesignerView::propertySet() { return d->sets ? d->sets->currentPropertySet() : 0; } void KexiTableDesignerView::slotBeforeCellChanged( KDbRecordData *data, int colnum, QVariant* newValue, KDbResultInfo* /*result*/) { if (!d->slotBeforeCellChanged_enabled) return; // qDebug() << d->view->selectedRecord() << " " << item //<< " " << d->sets->at( d->view->currentRecord() ) << " " << propertySet(); if (colnum == COLUMN_ID_CAPTION) {//'caption' //if 'type' is not filled yet if (data->at(COLUMN_ID_TYPE).isNull()) { //auto select 1st record of 'type' column d->view->data()->updateRecordEditBuffer(data, COLUMN_ID_TYPE, QVariant((int)0)); } KPropertySet *propertySetForRecord = d->sets->findPropertySetForItem(*data); if (propertySetForRecord) { d->addHistoryCommand_in_slotPropertyChanged_enabled = false; // because we'll add // the two changes as one group QString oldName(propertySetForRecord->property("name").value().toString()); QString oldCaption(propertySetForRecord->property("caption").value().toString()); //remember this action containing 2 subactions //Parent command is a Command containing 2 child commands Command *changeCaptionAndNameCommand = new Command( kundo2_i18n( "Change %1 field name to %2 " "and caption from %3 to %4", oldName, propertySetForRecord->property("name").value().toString(), oldCaption, newValue->toString()), 0, this ); //we need to create the action now as set["name"] will be changed soon. //Child 1 is the caption /*ChangeFieldPropertyCommand *changeCaptionCommand = */ (void)new ChangeFieldPropertyCommand(changeCaptionAndNameCommand, this, *propertySetForRecord, "caption", oldCaption, *newValue); //update field caption and name propertySetForRecord->changeProperty("caption", *newValue); propertySetForRecord->changeProperty("name", KDb::stringToIdentifier(newValue->toString())); //Child 2 is the name /*ChangeFieldPropertyCommand *changeNameCommand =*/ (void)new ChangeFieldPropertyCommand( changeCaptionAndNameCommand, this, *propertySetForRecord, "name", oldName, propertySetForRecord->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()->updateRecordEditBuffer(data, COLUMN_ID_ICON, QVariant()); d->view->data()->updateRecordEditBuffer(data, COLUMN_ID_CAPTION, QVariant(QString())); d->view->data()->updateRecordEditBuffer(data, COLUMN_ID_DESC, QVariant()); d->slotBeforeCellChanged_enabled = true; return; } KPropertySet *propertySetForRecord = d->sets->findPropertySetForItem(*data); if (!propertySetForRecord) return; KPropertySet &set = *propertySetForRecord; //propertySet(); //'type' col is changed (existed before) //-get type group number KDbField::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)KDbField::LastTypeGroup - 1) //don't show last (BLOB) type #else (int)KDbField::LastTypeGroup) #endif return; fieldTypeGroup = static_cast(i_fieldTypeGroup); //-get 1st type from this group, and update 'type' property KDbField::Type fieldType = KDb::defaultFieldTypeForGroup(fieldTypeGroup); if (fieldType == KDbField::InvalidType) fieldType = KDbField::Text; //-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==KDbField::BLOB) { // special case: BLOB type uses "mime-based" subtypes subTypeValue = slist.first(); } else {*/ subTypeValue = KDbField::typeString(fieldType); //} KProperty *subTypeProperty = &set["subType"]; qDebug() << subTypeProperty->value(); // *** this action contains subactions *** Command *changeDataTypeCommand = new Command( kundo2_i18n("Change data type for field %1 to %2", set["name"].value().toString(), KDbField::typeName(fieldType)), 0, this); //qDebug() << "++++++++++" << slist << nlist; //update subtype list and value const bool forcePropertySetReload = KDbField::typeGroup( KDbField::typeForString(subTypeProperty->value().toString())) != fieldTypeGroup; //<-- ????? const bool useListData = slist.count() > 1; 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 == KDbField::Boolean) { //! @todo maybe this is good for other data types as well? d->setPropertyValueIfNeeded(set, "notNull", QVariant(true), changeDataTypeCommand, false /*!forceAddCommand*/, false /*!rememberOldValue*/); d->setPropertyValueIfNeeded(set, "defaultValue", QVariant(false), changeDataTypeCommand, false /*!forceAddCommand*/, false /*!rememberOldValue*/); } if (set["primaryKey"].value().toBool() == true) { //primary keys require big int, so if selected type is not integer- remove PK if (fieldTypeGroup != KDbField::IntegerGroup) { /*not needed, line below will do the work d->view->data()->updateRecordEditBuffer(record, COLUMN_ID_ICON, QVariant()); d->view->data()->saveRecordChanges(record); */ //set["primaryKey"] = QVariant(false); d->setPropertyValueIfNeeded(set, "primaryKey", QVariant(false), changeDataTypeCommand); //! @todo should we display (passive?) dialog informing about cleared pkey? } } 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' KPropertySet *propertySetForRecord = d->sets->findPropertySetForItem(*data); if (!propertySetForRecord) return; //update field desc. QVariant oldValue((*propertySetForRecord)["description"].value()); qDebug() << oldValue; propertySetForRecord->changeProperty("description", *newValue); } } void KexiTableDesignerView::slotRecordUpdated(KDbRecordData *data) { const int record = d->view->data()->indexOf(data); if (record < 0) return; setDirty(); //-check if the record was empty before updating //if yes: we want to add a property set for this new record (field) QString fieldCaption(data->at(COLUMN_ID_CAPTION).toString()); const bool prop_set_allowed = !data->at(COLUMN_ID_TYPE).isNull(); if (!prop_set_allowed && d->sets->at(record)/*propertySet()*/) { //there is a property set, but it's not allowed - remove it: d->sets->eraseAt(record); //clear 'type' column: d->view->data()->clearRecordEditBuffer(); d->view->data()->updateRecordEditBuffer(data, COLUMN_ID_TYPE, QVariant()); d->view->data()->saveRecordChanges(data); } else if (prop_set_allowed && !d->sets->at(record)/*propertySet()*/) { //-- create a new field: KDbField::TypeGroup fieldTypeGroup = static_cast( data->at(COLUMN_ID_TYPE).toInt() + 1/*counting from 1*/); int intFieldType = KDb::defaultFieldTypeForGroup(fieldTypeGroup); if (intFieldType == 0) return; QString description(data->at(COLUMN_ID_DESC).toString()); //! @todo check uniqueness: QString fieldName(KDb::stringToIdentifier(fieldCaption)); KDbField::Type fieldType = KDb::intToFieldType(intFieldType); int maxLength = 0; if (fieldType == KDbField::Text) { maxLength = KDbField::defaultMaxLength(); } KDbField field( //tmp fieldName, fieldType, KDbField::NoConstraints, KDbField::NoOptions, maxLength, /*precision*/0, /*defaultValue*/QVariant(), fieldCaption, description); // reasonable case for boolean type: set notNull flag and "false" as default value switch (fieldType) { case KDbField::Boolean: field.setNotNull(true); field.setDefaultValue(QVariant(false)); break; case KDbField::Text: field.setMaxLengthStrategy(KDbField::DefaultMaxLength); break; default:; } qDebug() << field; //create a new property set: KPropertySet *newSet = createPropertySet(record, field, true); //refresh property editor: propertySetSwitched(); if (d->addHistoryCommand_in_slotRecordUpdated_enabled) { addHistoryCommand(new InsertFieldCommand(0, this, record, *newSet /*propertySet()*/), //, field /*will be copied*/ false /* !execute */); } } } void KexiTableDesignerView::updateActions() { updateActions(false); } void KexiTableDesignerView::slotPropertyChanged(KPropertySet& set, KProperty& property) { const QByteArray pname(property.name()); qDebug() << pname << " = " << property.value() << " (oldvalue = " << property.oldValue() << ")"; // true if PK should be altered bool changePrimaryKey = false; // true if 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 record = d->sets->findRecordForPropertyValue("uid", set["uid"].value().toInt()); KDbRecordData *data = d->view->recordAt(record); if (data) d->updateIconForRecord(data, &set); } //setting autonumber requires setting PK as well Command *setAutonumberCommand = 0; Command *toplevelCommand = 0; if (pname == "autoIncrement" && property.value().toBool() == true) { if (set["primaryKey"].value().toBool() == false) {//we need PKEY here! QString msg = xi18n("Setting autonumber requires primary key to be set for current field."); if (d->primaryKeyExists) msg += xi18n("Previous primary key will be removed."); msg += xi18n("Do you want to create primary key for current field? " "Click Cancel to cancel setting autonumber."); if (KMessageBox::Yes == KMessageBox::questionYesNo(this, msg, xi18n("Setting Autonumber Field"), KGuiItem(xi18nc("@action:button", "Create &Primary Key"), KexiIconName("database-key")), KStandardGuiItem::cancel())) { changePrimaryKey = true; setPrimaryKey = true; //switchPrimaryKey(set, true); // this will be toplevel command setAutonumberCommand = new Command( kundo2_i18n("Set autonumber for field %1", set["name"].value().toString()), 0, this); toplevelCommand = setAutonumberCommand; d->setPropertyValueIfNeeded(set, "autoIncrement", QVariant(true), setAutonumberCommand); } else { setAutonumberCommand = new Command( kundo2_i18n("Remove autonumber from field %1", set["name"].value().toString()), 0, this); d->setPropertyValueIfNeeded(set, "autoIncrement", QVariant(false), 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 Command *unsetIndexedOrUniquOrNotNullCommand = new Command( kundo2_i18n("Set %1 property for field %2", property.caption(), set["name"].value().toString()), 0, this); toplevelCommand = unsetIndexedOrUniquOrNotNullCommand; d->setPropertyValueIfNeeded(set, pname, QVariant(false), unsetIndexedOrUniquOrNotNullCommand); if (pname == "notNull") { d->setPropertyValueIfNeeded(set, "unique", QVariant(false), unsetIndexedOrUniquOrNotNullCommand); } } if (pname == "defaultValue") { KDbField::Type type = KDb::intToFieldType(set["type"].value().toInt()); set["defaultValue"].setType(static_cast(KDbField::variantType(type))); } if (pname == "subType" && d->slotPropertyChanged_subType_enabled) { d->slotPropertyChanged_subType_enabled = false; if (set["primaryKey"].value().toBool() == true && property.value().toString() != KDbField::typeString(KDbField::BigInteger)) { qDebug() << "INVALID " << property.value().toString(); // if (KMessageBox::Yes == KMessageBox::questionYesNo(this, msg, // xi18n("This field has primary key assigned. Setting autonumber field"), // KGuiItem(xi18nc("@action:button", "Create &Primary Key"), KexiIconName("database-key")), KStandardGuiItem::cancel() )) } KDbField::Type type = KDb::intToFieldType(set["type"].value().toInt()); QString typeName; /* disabled - "mime" is moved from subType to "objectType" custom property if (type==KDbField::BLOB) { //special case //find i18n'd text QStringList stringsList, namesList; getSubTypeListData(KDbField::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 = KDbField::typeName(KDbField::typeForString(property.value().toString())); Command* changeFieldTypeCommand = new Command( kundo2_i18n( "Change type for field %1 to %2", set["name"].value().toString(), typeName), 0, this); d->setPropertyValueIfNeeded(set, "subType", property.value(), property.oldValue(), changeFieldTypeCommand); qDebug() << set["type"].value(); const KDbField::Type newType = KDbField::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, KDbField::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; } //! @todo add command text if ( d->addHistoryCommand_in_slotPropertyChanged_enabled && !changePrimaryKey/*we'll add multiple commands for PK*/) { addHistoryCommand(new ChangeFieldPropertyCommand(0, this, set, property.name(), property.oldValue() /* ??? */, property.value()), false /* !execute */); } if (changePrimaryKey) { d->slotPropertyChanged_primaryKey_enabled = false; if (setPrimaryKey) { //primary key implies some rules //this action contains subactions Command * setPrimaryKeyCommand = new Command( kundo2_i18n("Set primary key for field %1", set["name"].value().toString()), toplevelCommand, this); if (!toplevelCommand) { toplevelCommand = setPrimaryKeyCommand; } d->setPropertyValueIfNeeded(set, "primaryKey", QVariant(true), setPrimaryKeyCommand, true /*forceAddCommand*/); d->setPropertyValueIfNeeded(set, "unique", QVariant(true), setPrimaryKeyCommand); d->setPropertyValueIfNeeded(set, "notNull", QVariant(true), setPrimaryKeyCommand); d->setPropertyValueIfNeeded(set, "allowEmpty", QVariant(false), setPrimaryKeyCommand); d->setPropertyValueIfNeeded(set, "indexed", QVariant(true), setPrimaryKeyCommand); //! \todo: add setting for this: "Integer PKeys have autonumber set by default" d->setPropertyValueIfNeeded(set, "autoIncrement", QVariant(true), setPrimaryKeyCommand); /* set["unique"] = QVariant(true); set["notNull"] = QVariant(true); set["allowEmpty"] = QVariant(false); set["indexed"] = QVariant(true); set["autoIncrement"] = QVariant(true);*/ } else { // set PK to false //remember this action containing 2 subactions Command *setPrimaryKeyCommand = new Command( kundo2_i18n("Unset primary key for field %1", set["name"].value().toString()), toplevelCommand, this); if (!toplevelCommand) { toplevelCommand = setPrimaryKeyCommand; } d->setPropertyValueIfNeeded(set, "primaryKey", QVariant(false), setPrimaryKeyCommand, true /*forceAddCommand*/); d->setPropertyValueIfNeeded(set, "autoIncrement", QVariant(false), setPrimaryKeyCommand); } switchPrimaryKey(set, setPrimaryKey, true/*wasPKey*/, toplevelCommand); d->updatePropertiesVisibility( KDbField::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::slotRecordInserted() { updateActions(); if (d->addHistoryCommand_in_slotRecordInserted_enabled) { const int record = d->view->currentRecord(); if (record >= 0) { addHistoryCommand(new InsertEmptyRecordCommand(0, this, record), false /* !execute */); } } //! @todo } void KexiTableDesignerView::slotAboutToDeleteRecord( KDbRecordData* data, KDbResultInfo* result, bool repaint) { Q_UNUSED(result) Q_UNUSED(repaint) if ((*data)[COLUMN_ID_ICON].toString() == KexiIconName("database-key")) d->primaryKeyExists = false; if (d->addHistoryCommand_in_slotAboutToDeleteRecord_enabled) { const int record = d->view->data()->indexOf(data); KPropertySet *set = record >= 0 ? d->sets->at(record) : 0; //set can be 0 here, what means "removing empty record" addHistoryCommand( new RemoveFieldCommand(0, this, record, set), false /* !execute */ ); } } KDbField * KexiTableDesignerView::buildField(const KPropertySet &set) const { //create a map of property values const KDbField::Type type = KDb::intToFieldType(set["type"].value().toInt()); QMap values(set.propertyValues()); //qDebug() << values; //remove internal values, to avoid creating custom field's properties KDbField *field = new KDbField(); for (QMutableMapIterator it(values); it.hasNext();) { it.next(); const QByteArray propName(it.key()); if (d->internalPropertyNames.contains(propName) || propName.startsWith("this:") || (/*sanity*/propName == "objectType" && type != KDbField::BLOB) || (propName == "unsigned" && !KDbField::isIntegerType(type)) || (propName == "maxLength" && type != KDbField::Text) || (propName == "precision" && !KDbField::isFPNumericType(type)) || (propName == "scale" && !KDbField::isFPNumericType(type)) ) { it.remove(); } } //assign properties to the field // (note that "objectType" property will be saved as custom property) if (!KDb::setFieldProperties(field, values)) { delete field; return 0; } return field; } tristate KexiTableDesignerView::buildSchema(KDbTableSchema &schema, bool beSilent) { if (!d->view->acceptRecordEditing()) return cancelled; //check for missing captions KPropertySet *b = 0; bool no_fields = true; int i; QSet names; 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().toLower()); if (name.isEmpty()) { if (beSilent) { qWarning() << QString("no field caption entered at record %1...").arg(i + 1); } else { d->view->setCursorPosition(i, COLUMN_ID_CAPTION); d->view->startEditCurrentCell(); KMessageBox::information(this, xi18n("You should enter field caption.")); } return cancelled; } if (names.contains(name)) { break; } names.insert(name); //remember } } //check for empty design if (no_fields) { if (beSilent) { qWarning() << "no field defined..."; } else { KMessageBox::sorry(this, xi18n("You have added no fields.\nEvery table should have at least one field.")); } return cancelled; } //check for duplicates if (b && i < (int)d->sets->size()) { if (beSilent) { qWarning() << QString("duplicated field name '%1'") .arg((*b)["name"].value().toString()); } 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, xi18nc("@info", "You have added %1 field name twice." "Field names cannot be repeated. Correct name of the field.", (*b)["name"].value().toString())); } return cancelled; } //check for pkey; automatically add a pkey if user wanted if (!d->primaryKeyExists) { if (beSilent) { qDebug() << "no primay key defined..."; } else { const int questionRes = KMessageBox::questionYesNoCancel(this, xi18nc("@info", "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 a primary key to be automatically added now?" "If you want to add a primary key by hand, press Cancel " "to cancel saving table design.", schema.name()), QString(), KGuiItem(xi18nc("@action:button Add Database Primary Key to a Table", "&Add Primary Key"), KexiIconName("database-key")), KGuiItem(xi18nc("@action:button Do Not Add Database Primary Key to a Table", "Do &Not Add"), KStandardGuiItem::no().icon()), KStandardGuiItem::cancel(), "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"); KLocalizedString pkFieldCaption(kxi18nc("Identifier%1", "Id%1")); while (i < (int)d->sets->size()) { KPropertySet *set = d->sets->at(i); if (set) { if ( (*set)["name"].value().toString() == pkFieldName.arg(idIndex == 1 ? QString() : QString::number(idIndex)) || (*set)["caption"].value().toString() == pkFieldCaption.subs(idIndex == 1 ? QString() : QString::number(idIndex)).toString() ) { //try next id index i = 0; idIndex++; continue; } } i++; } pkFieldName = pkFieldName.arg(idIndex == 1 ? QString() : QString::number(idIndex)); //ok, add PK with such unique name d->view->insertEmptyRecord(0); d->view->setCursorPosition(0, COLUMN_ID_CAPTION); d->view->data()->updateRecordEditBuffer(d->view->selectedRecord(), COLUMN_ID_CAPTION, pkFieldCaption.subs(idIndex == 1 ? QString() : QString::number(idIndex)).toString() ); d->view->data()->updateRecordEditBuffer(d->view->selectedRecord(), COLUMN_ID_TYPE, QVariant(KDbField::IntegerGroup - 1/*counting from 0*/)); if (!d->view->data()->saveRecordChanges(d->view->selectedRecord(), true)) { return cancelled; } slotTogglePrimaryKey(); } } } //we're ready... for (i = 0;i < (int)d->sets->size();++i) {//for every field create Field definition KPropertySet *s = d->sets->at(i); if (!s) continue; KDbField * f = buildField(*s); if (!f) continue; //hmm? if (!schema.addField(f)) { qWarning() << "!schema.addField(f)"; return false; } if ( !(*s)["rowSource"].value().toString().isEmpty() && !(*s)["rowSourceType"].value().toString().isEmpty()) { //add lookup column KDbLookupFieldSchema *lookupFieldSchema = new KDbLookupFieldSchema(); KDbLookupFieldSchemaRecordSource recordSource; recordSource.setTypeByName((*s)["rowSourceType"].value().toString()); recordSource.setName((*s)["rowSource"].value().toString()); lookupFieldSchema->setRecordSource(recordSource); 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 kproperty editor needed QList visibleColumns; const int visibleColumn = (*s)["visibleColumn"].value().toInt(); if (visibleColumn >= 0) visibleColumns.append(visibleColumn); lookupFieldSchema->setVisibleColumns(visibleColumns); //! @todo support columnWidths(), columnHeadersVisible(), maxVisibleRecords(), limitToList(), displayWidget() if (!schema.setLookupFieldSchema(f->name(), lookupFieldSchema)) { qWarning() << "!schema.setLookupFieldSchema()"; delete lookupFieldSchema; return false; } } } return true; } //! @internal //! A recursive function for copying alter table actions from undo/redo commands. static void copyAlterTableActions(const KUndo2Command* command, KDbAlterTableHandler::ActionList &actions) { for (int i = 0; i < command->childCount(); ++i) { copyAlterTableActions(command->child(i), actions); } const Command* cmd = dynamic_cast(command); if (!cmd) { qWarning() << "cmd is not of type 'Command'!"; return; } KDbAlterTableHandler::ActionBase* action = cmd->createAction(); //some commands can contain null actions, e.g. "set visibility" command if (action) actions.append(action); } tristate KexiTableDesignerView::buildAlterTableActions( KDbAlterTableHandler::ActionList &actions) { actions.clear(); qDebug() << d->history->count() << " top-level command(s) to process..."; for (int i = 0; i < d->history->count(); ++i) { copyAlterTableActions(d->history->command(i), actions); } return true; } KDbObject* KexiTableDesignerView::storeNewData(const KDbObject& object, KexiView::StoreNewDataOptions options, bool *cancel) { Q_ASSERT(cancel); if (tempData()->table() || window()->schemaObject()) //must not be return 0; //create table schema definition tempData()->setTable(new KDbTableSchema(object.name())); tempData()->table()->setName(object.name()); tempData()->table()->setCaption(object.caption()); tempData()->table()->setDescription(object.description()); tristate res = buildSchema(*tempData()->table()); *cancel = ~res; //FINALLY: create table: if (res == true) { //! @todo KDbConnection *conn = KexiMainWindowIface::global()->project()->dbConnection(); - res = conn->createTable(tempData()->table(), options & KexiView::OverwriteExistingData); + KDbConnection::CreateTableOptions createOptions(KDbConnection::CreateTableOption::Default); + if (options & KexiView::OverwriteExistingData) { + createOptions |= KDbConnection::CreateTableOption::DropDestination; + } + res = conn->createTable(tempData()->table(), createOptions); if (res == true) { res = KexiMainWindowIface::global()->project()->removeUserDataBlock(tempData()->table()->id()); } else { window()->setStatus(conn, ""); } } if (res == true) { //we've current schema tempData()->tableSchemaChangedInPreviousView = true; d->history->clear(); } else { KDbTableSchema *tableToDelete = tempData()->table(); tempData()->setTable(nullptr); delete tableToDelete; } return tempData()->table(); } KDbObject* KexiTableDesignerView::copyData(const KDbObject& object, KexiView::StoreNewDataOptions options, bool *cancel) { Q_ASSERT(cancel); Q_UNUSED(options); Q_UNUSED(cancel); if (!tempData()->table()) { qWarning() << "Cannot copy data without source table (tempData()->table)"; return 0; } KDbConnection *conn = KexiMainWindowIface::global()->project()->dbConnection(); KDbTableSchema *copiedTable = conn->copyTable(*tempData()->table(), object); if (!copiedTable) { return 0; } if (!KexiMainWindowIface::global()->project()->copyUserDataBlock(tempData()->table()->id(), copiedTable->id())) { conn->dropTable(copiedTable); delete copiedTable; return 0; } return copiedTable; } tristate KexiTableDesignerView::storeData(bool dontAsk) { if (!tempData()->table() || !window()->schemaObject()) { d->recentResultOfStoreData = false; return false; } KDbConnection *conn = KexiMainWindowIface::global()->project()->dbConnection(); KDbAlterTableHandler *alterTableHandler = 0; KDbTableSchema *newTable = 0; //- create action list for the alter table handler KDbAlterTableHandler::ActionList actions; tristate res = buildAlterTableActions(actions); //!< @todo this is temporary flag before we switch entirely to real alter table bool realAlterTableCanBeUsed = false; if (res == true) { alterTableHandler = new KDbAlterTableHandler(conn); alterTableHandler->setActions(actions); if (!d->tempStoreDataUsingRealAlterTable) { //only compute requirements KDbAlterTableHandler::ExecutionArguments args; args.onlyComputeRequirements = true; (void)alterTableHandler->execute(tempData()->table()->name(), &args); res = args.result; if ( res == true && 0 == (args.requirements & (0xffff ^ KDbAlterTableHandler::SchemaAlteringRequired))) { realAlterTableCanBeUsed = true; } } } if (res == true) { res = KexiTablePart::askForClosingObjectsUsingTableSchema( window(), conn, tempData()->table(), xi18nc("@info", "You are about to change the design of table %1 " "but following objects using this table are opened:", 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).toString(); 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 KDbTableSchema(); // copy the object data static_cast(*newTable) = static_cast(*tempData()->table()); res = buildSchema(*newTable); qDebug() << "BUILD SCHEMA:" << *newTable; res = conn->alterTable(tempData()->table(), newTable); if (res != true) window()->setStatus(conn, ""); } else { KDbAlterTableHandler::ExecutionArguments args; newTable = alterTableHandler->execute(tempData()->table()->name(), &args); res = args.result; qDebug() << "ALTER TABLE EXECUTE: " << res.toString(); if (true != res) { qDebug() << alterTableHandler->result(); window()->setStatus(alterTableHandler, ""); } } } if (res == true) { //change current schema tempData()->setTable(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 (KexiMainWindowIface::global()->currentWindow() != window()) { //to avoid executing for multiple alter table views return false; } if (!tempData()->table() || !window()->schemaObject()) return false; KDbConnection *conn = KexiMainWindowIface::global()->project()->dbConnection(); KDbAlterTableHandler::ActionList actions; /*tristate res =*/ buildAlterTableActions(actions); //! @todo result? KDbAlterTableHandler alterTableHandler(conn); alterTableHandler.setActions(actions); KDbAlterTableHandler::ExecutionArguments args; if (debugTarget) { args.debugString = debugTarget; } else { args.simulate = true; } (void)alterTableHandler.execute(tempData()->table()->name(), &args); return args.result; # else Q_UNUSED(debugTarget); return false; # endif #else Q_UNUSED(debugTarget); return false; #endif } void KexiTableDesignerView::slotSimulateAlterTableExecution() { (void)simulateAlterTableExecution(0); } tristate KexiTableDesignerView::executeRealAlterTable() { d->tempStoreDataUsingRealAlterTable = true; d->recentResultOfStoreData = false; // will call KexiMainWindow::slotProjectSaveAs() and thus storeData(): QMetaObject::invokeMethod( KexiMainWindowIface::global()->thisWidget(), "slotProjectSave"); d->tempStoreDataUsingRealAlterTable = false; return d->recentResultOfStoreData; } KexiTablePartTempData* KexiTableDesignerView::tempData() const { return static_cast(window()->data()); } #ifdef KEXI_DEBUG_GUI void KexiTableDesignerView::debugCommand(const KUndo2Command* command, int nestingLevel) { const Command* kexiCommand = dynamic_cast(command); if (kexiCommand) { KDb::alterTableActionDebugGUI(kexiCommand->debugString(), nestingLevel); } else { KDb::alterTableActionDebugGUI(command->text().toString(), nestingLevel); } //show subcommands for (int i = 0; i < command->childCount(); ++i) { debugCommand(command->child(i), nestingLevel + 1); } } #endif void KexiTableDesignerView::addHistoryCommand(KexiTableDesignerCommands::Command* command, bool execute) { #ifdef KEXI_NO_UNDOREDO_ALTERTABLE Q_UNUSED(command); Q_UNUSED(execute); #else # ifdef KEXI_DEBUG_GUI debugCommand(command, 0); # endif if (!execute) { command->blockRedoOnce(); } d->history->push(command); 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 KDb::alterTableActionDebugGUI(QString("UNDO:")); # endif d->history->undo(); updateUndoRedoActions(); #endif } void KexiTableDesignerView::slotRedo() { #ifndef KEXI_NO_UNDOREDO_ALTERTABLE # ifdef KEXI_DEBUG_GUI KDb::alterTableActionDebugGUI(QString("REDO:")); # endif d->history->redo(); updateUndoRedoActions(); #endif } void KexiTableDesignerView::slotAboutToShowContextMenu() { //update title QString title; if (propertySet()) { const KPropertySet &set = *propertySet(); QString captionOrName(set["caption"].value().toString()); if (captionOrName.isEmpty()) captionOrName = set["name"].value().toString(); title = xi18nc("@info", "Table field %1", captionOrName); } else { title = xi18nc("Empty table row", "Empty Row"); } //! \todo replace lineedit with table_field icon d->view->setContextMenuTitle(KexiIcon("lineedit"), title); } QString KexiTableDesignerView::debugStringForCurrentTableSchema(tristate& result) { KDbTableSchema tempTable; //copy object data static_cast(tempTable) = static_cast(*tempData()->table()); result = buildSchema(tempTable, true /*beSilent*/); if (true != result) { return QString(); } return KDbUtils::debugString(tempTable); } // -- low-level actions used by undo/redo framework void KexiTableDesignerView::clearRecord(int record, bool addCommand) { if (!d->view->acceptRecordEditing()) return; KDbRecordData *data = d->view->recordAt(record); if (!data) return; //clear from prop. set d->sets->eraseAt(record); //clear record in table view (just clear value in COLUMN_ID_TYPE column) if (!addCommand) { d->addHistoryCommand_in_slotRecordUpdated_enabled = false; d->addHistoryCommand_in_slotPropertyChanged_enabled = false; d->slotBeforeCellChanged_enabled = false; } d->view->data()->updateRecordEditBuffer(data, COLUMN_ID_TYPE, QVariant()); if (!addCommand) { d->addHistoryCommand_in_slotRecordUpdated_enabled = true; d->addHistoryCommand_in_slotPropertyChanged_enabled = true; d->slotBeforeCellChanged_enabled = true; } d->view->data()->saveRecordChanges(data, true); } void KexiTableDesignerView::insertField(int record, const QString& caption, bool addCommand) { insertFieldInternal(record, 0, caption, addCommand); } void KexiTableDesignerView::insertField(int record, KPropertySet& set, bool addCommand) { insertFieldInternal(record, &set, QString(), addCommand); } void KexiTableDesignerView::insertFieldInternal(int record, KPropertySet* set, //const KDbField& field, const QString& caption, bool addCommand) { if (set && (!set->contains("type") || !set->contains("caption"))) { qWarning() << "no 'type' or 'caption' property in set!"; return; } if (!d->view->acceptRecordEditing()) return; KDbRecordData *data = d->view->recordAt(record); if (!data) return; if (!addCommand) { d->addHistoryCommand_in_slotRecordUpdated_enabled = false; d->addHistoryCommand_in_slotPropertyChanged_enabled = false; d->slotBeforeCellChanged_enabled = false; } d->view->data()->updateRecordEditBuffer(data, COLUMN_ID_CAPTION, set ? (*set)["caption"].value() : QVariant(caption)); KDbField::TypeGroup tg; if (set) { tg = KDbField::typeGroup(KDb::intToFieldType((*set)["type"].value().toInt())); } else { tg = KDbField::TextGroup; // default type } d->view->data()->updateRecordEditBuffer(data, COLUMN_ID_TYPE, int(tg) - 1); d->view->data()->updateRecordEditBuffer(data, COLUMN_ID_DESC, set ? (*set)["description"].value() : QVariant()); if (!addCommand) { d->slotBeforeCellChanged_enabled = true; } //this will create a new property set: d->view->data()->saveRecordChanges(data); if (set) { KPropertySet *newSet = d->sets->at(record); if (newSet) { *newSet = *set; //deep copy } else { qWarning() << "!newSet, record==" << record; } } if (!addCommand) { d->addHistoryCommand_in_slotPropertyChanged_enabled = true; d->addHistoryCommand_in_slotRecordUpdated_enabled = true; } d->view->updateRecord(record); propertySetReloaded(true); } void KexiTableDesignerView::insertEmptyRecord(int record, bool addCommand) { if (!addCommand) { d->addHistoryCommand_in_slotRecordInserted_enabled = false; } d->view->insertEmptyRecord(record); if (!addCommand) { d->addHistoryCommand_in_slotRecordInserted_enabled = true; } } void KexiTableDesignerView::deleteRecord(int record, bool addCommand) { KDbRecordData *data = d->view->recordAt(record); if (!data) return; if (!addCommand) { d->addHistoryCommand_in_slotAboutToDeleteRecord_enabled = false; } const bool res = d->view->deleteItem(data); if (!addCommand) { d->addHistoryCommand_in_slotAboutToDeleteRecord_enabled = true; } if (!res) return; } void KexiTableDesignerView::changeFieldPropertyForRecord(int record, const QByteArray& propertyName, const QVariant& newValue, KPropertyListData* const listData, bool addCommand) { #ifdef KEXI_DEBUG_GUI KDb::alterTableActionDebugGUI(QString("** changeFieldProperty: \"") + QString(propertyName) + "\" to \"" + newValue.toString() + "\"", 2/*nestingLevel*/); #endif if (!d->view->acceptRecordEditing()) return; KPropertySet* set = d->sets->at(record); if (!set || !set->contains(propertyName)) return; KProperty &property = set->property(propertyName); if (listData) { if (listData->keys.isEmpty()) property.setListData(0); else property.setListData(new KPropertyListData(*listData)); } if (propertyName != "type") //delayed type update (we need to have subtype set properly) property.setValue(newValue); KDbRecordData *data = d->view->recordAt(record); Q_ASSERT(data); if (propertyName == "type") { d->slotPropertyChanged_subType_enabled = false; d->view->data()->updateRecordEditBuffer(data, COLUMN_ID_TYPE, int(KDbField::typeGroup(KDb::intToFieldType(newValue.toInt()))) - 1); d->view->data()->saveRecordChanges(data); d->addHistoryCommand_in_slotRecordUpdated_enabled = true; property.setValue(newValue); //delayed type update (we needed to have subtype set properly) } if (!addCommand) { d->addHistoryCommand_in_slotRecordUpdated_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()->updateRecordEditBuffer(data, COLUMN_ID_CAPTION, newValue); d->view->data()->saveRecordChanges(data); if (!addCommand) { d->slotBeforeCellChanged_enabled = true; } } else if (propertyName == "description") { if (!addCommand) { d->slotBeforeCellChanged_enabled = false; } d->view->data()->updateRecordEditBuffer(data, COLUMN_ID_DESC, newValue); if (!addCommand) { d->slotBeforeCellChanged_enabled = true; } d->view->data()->saveRecordChanges(data); } if (!addCommand) { d->addHistoryCommand_in_slotPropertyChanged_enabled = true; d->addHistoryCommand_in_slotRecordUpdated_enabled = true; d->slotPropertyChanged_subType_enabled = true; } d->view->updateRecord(record); } void KexiTableDesignerView::changeFieldProperty(int fieldUID, const QByteArray& propertyName, const QVariant& newValue, KPropertyListData* const listData, bool addCommand) { //find a property by UID const int record = d->sets->findRecordForPropertyValue("uid", fieldUID); if (record < 0) { qWarning() << "field with uid=" << fieldUID << " not found!"; return; } changeFieldPropertyForRecord(record, propertyName, newValue, listData, addCommand); } void KexiTableDesignerView::changePropertyVisibility( int fieldUID, const QByteArray& propertyName, bool visible) { #ifdef KEXI_DEBUG_GUI KDb::alterTableActionDebugGUI(QString("** changePropertyVisibility: \"") + QString(propertyName) + "\" to \"" + (visible ? "true" : "false") + "\"", 2/*nestingLevel*/); #endif if (!d->view->acceptRecordEditing()) return; //find a property by name const int record = d->sets->findRecordForPropertyValue("uid", fieldUID); if (record < 0) return; KPropertySet* set = d->sets->at(record); if (!set || !set->contains(propertyName)) return; KProperty &property = set->property(propertyName); if (property.isVisible() != visible) { property.setVisible(visible); propertySetReloaded(true); } } void KexiTableDesignerView::propertySetSwitched() { KexiDataTableView::propertySetSwitched(); KexiLookupColumnPage *page = qobject_cast(window()->part())->lookupColumnPage(); if (page) page->assignPropertySet(propertySet()); } bool KexiTableDesignerView::isPhysicalAlteringNeeded() { //- create action list for the alter table handler KDbAlterTableHandler::ActionList actions; tristate res = buildAlterTableActions(actions); if (res != true) return true; KDbConnection *conn = KexiMainWindowIface::global()->project()->dbConnection(); KDbAlterTableHandler *alterTableHandler = new KDbAlterTableHandler(conn); alterTableHandler->setActions(actions); //only compute requirements KDbAlterTableHandler::ExecutionArguments args; args.onlyComputeRequirements = true; (void)alterTableHandler->execute(tempData()->table()->name(), &args); res = args.result; delete alterTableHandler; if ( res == true && 0 == (args.requirements & (0xffff ^ KDbAlterTableHandler::SchemaAlteringRequired))) { return false; } return true; }