You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1024 lines
29 KiB
1024 lines
29 KiB
/* This file is part of the KDE project
|
|
Copyright (C) 2003 Lucijan Busch <lucijan@kde.org>
|
|
Copyright (C) 2003-2006 Jaroslaw Staniek <js@iidea.pl>
|
|
|
|
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 <tqfile.h>
|
|
#include <tqapplication.h>
|
|
#include <tqdom.h>
|
|
|
|
#include <kmimetype.h>
|
|
#include <kdebug.h>
|
|
#include <klocale.h>
|
|
|
|
#include <kexiutils/identifier.h>
|
|
|
|
#include <kexidb/connection.h>
|
|
#include <kexidb/cursor.h>
|
|
#include <kexidb/driver.h>
|
|
#include <kexidb/drivermanager.h>
|
|
#include <kexidb/utils.h>
|
|
#include <kexidb/parser/parser.h>
|
|
#include <kexidb/msghandler.h>
|
|
#include <kexidb/dbproperties.h>
|
|
#include <kexiutils/utils.h>
|
|
|
|
#include "kexiproject.h"
|
|
#include "kexipartmanager.h"
|
|
#include "kexipartitem.h"
|
|
#include "kexipartinfo.h"
|
|
#include "kexipart.h"
|
|
#include "kexidialogbase.h"
|
|
#include "kexi.h"
|
|
#include "keximainwindow.h"
|
|
#include "kexiblobbuffer.h"
|
|
#include "kexiguimsghandler.h"
|
|
|
|
#include <assert.h>
|
|
|
|
class KexiProject::Private
|
|
{
|
|
public:
|
|
Private()
|
|
: data(0)
|
|
, itemDictsCache(199)
|
|
, unstoredItems(199)
|
|
, tempPartItemID_Counter(-1)
|
|
, sqlParser(0)
|
|
, versionMajor(0)
|
|
, versionMinor(0)
|
|
{
|
|
itemDictsCache.setAutoDelete(true);
|
|
unstoredItems.setAutoDelete(true);
|
|
}
|
|
~Private() {
|
|
delete data;
|
|
data=0;
|
|
delete sqlParser;
|
|
}
|
|
|
|
TQGuardedPtr<KexiDB::Connection> connection;
|
|
TQGuardedPtr<KexiProjectData> data;
|
|
|
|
TQString error_title;
|
|
|
|
//! a cache for item() method, indexed by project part's ids
|
|
TQIntDict<KexiPart::ItemDict> itemDictsCache;
|
|
|
|
TQPtrDict<KexiPart::Item> unstoredItems;
|
|
int tempPartItemID_Counter; //!< helper for getting unique
|
|
//!< temporary identifiers for unstored items
|
|
KexiDB::Parser* sqlParser;
|
|
|
|
int versionMajor;
|
|
int versionMinor;
|
|
};
|
|
|
|
//---------------------------
|
|
|
|
/*
|
|
Helper for setting temporary error title.
|
|
class KexiProject::ErrorTitle
|
|
{
|
|
public:
|
|
ErrorTitle(KexiProject* p, const TQString& msg = TQString())
|
|
: prj(p)
|
|
, prev_err_title(p->m_error_title)
|
|
{
|
|
p->m_error_title = msg;
|
|
}
|
|
~ErrorTitle()
|
|
{
|
|
prj->m_error_title = prev_err_title;
|
|
}
|
|
KexiProject* prj;
|
|
TQString prev_err_title;
|
|
};*/
|
|
|
|
KexiProject::KexiProject(KexiProjectData *pdata, KexiDB::MessageHandler* handler)
|
|
: TQObject(), Object(handler)
|
|
, d(new Private())
|
|
{
|
|
d->data = pdata;
|
|
//! @todo partmanager is outside project, so can be initialised just once:
|
|
Kexi::partManager().lookup();
|
|
}
|
|
|
|
KexiProject::KexiProject(KexiProjectData *pdata, KexiDB::MessageHandler* handler,
|
|
KexiDB::Connection* conn)
|
|
: TQObject(), Object(handler)
|
|
, d(new Private())
|
|
{
|
|
d->data = pdata;
|
|
if (d->data->connectionData() == d->connection->data())
|
|
d->connection = conn;
|
|
else
|
|
kdWarning() << "KexiProject::KexiProject(): passed connection's data ("
|
|
<< conn->data()->serverInfoString() << ") is not compatible with project's conn. data ("
|
|
<< d->data->connectionData()->serverInfoString() << ")" << endl;
|
|
//! @todo partmanager is outside project, so can be initialised just once:
|
|
Kexi::partManager().lookup();
|
|
}
|
|
|
|
KexiProject::~KexiProject()
|
|
{
|
|
closeConnection();
|
|
delete d;
|
|
}
|
|
|
|
KexiDB::Connection *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)
|
|
{
|
|
return openInternal(&incompatibleWithKexi);
|
|
}
|
|
|
|
tristate
|
|
KexiProject::open()
|
|
{
|
|
return openInternal(0);
|
|
}
|
|
|
|
tristate
|
|
KexiProject::openInternal(bool *incompatibleWithKexi)
|
|
{
|
|
if (incompatibleWithKexi)
|
|
*incompatibleWithKexi = false;
|
|
kdDebug() << "KexiProject::open(): " << d->data->databaseName() <<" "<< d->data->connectionData()->driverName << endl;
|
|
KexiDB::MessageTitle et(this,
|
|
i18n("Could not open project \"%1\".").arg(d->data->databaseName()));
|
|
|
|
if (!createConnection()) {
|
|
kdDebug() << "KexiProject::open(): !createConnection()" << endl;
|
|
return false;
|
|
}
|
|
bool cancel = false;
|
|
KexiGUIMessageHandler msgHandler;
|
|
if (!d->connection->useDatabase(d->data->databaseName(), true, &cancel, &msgHandler))
|
|
{
|
|
if (cancel) {
|
|
return cancelled;
|
|
}
|
|
kdDebug() << "KexiProject::open(): !d->connection->useDatabase() "
|
|
<< d->data->databaseName() <<" "<< d->data->connectionData()->driverName << endl;
|
|
|
|
if (d->connection->errorNum() == ERR_NO_DB_PROPERTY) {
|
|
//<temp>
|
|
//! @todo this is temporary workaround as we have no import driver for SQLite
|
|
if (/*supported?*/ !d->data->connectionData()->driverName.lower().startsWith("sqlite")) {
|
|
//</temp>
|
|
if (incompatibleWithKexi)
|
|
*incompatibleWithKexi = true;
|
|
}
|
|
else
|
|
setError(d->connection);
|
|
closeConnection();
|
|
return false;
|
|
}
|
|
|
|
setError(d->connection);
|
|
closeConnection();
|
|
return false;
|
|
}
|
|
|
|
if (!initProject())
|
|
return false;
|
|
|
|
return createInternalStructures(/*insideTransaction*/true);
|
|
}
|
|
|
|
tristate
|
|
KexiProject::create(bool forceOverwrite)
|
|
{
|
|
KexiDB::MessageTitle et(this,
|
|
i18n("Could not create project \"%1\".").arg(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() )) {
|
|
setError(d->connection);
|
|
closeConnection();
|
|
return false;
|
|
}
|
|
kdDebug() << "--- DB '" << d->data->databaseName() << "' dropped ---"<< endl;
|
|
}
|
|
if (!d->connection->createDatabase( d->data->databaseName() )) {
|
|
setError(d->connection);
|
|
closeConnection();
|
|
return false;
|
|
}
|
|
kdDebug() << "--- DB '" << d->data->databaseName() << "' created ---"<< endl;
|
|
// and now: open
|
|
if (!d->connection->useDatabase(d->data->databaseName()))
|
|
{
|
|
kdDebug() << "--- DB '" << d->data->databaseName() << "' USE ERROR ---"<< endl;
|
|
setError(d->connection);
|
|
closeConnection();
|
|
return false;
|
|
}
|
|
kdDebug() << "--- DB '" << d->data->databaseName() << "' used ---"<< endl;
|
|
|
|
//<add some data>
|
|
KexiDB::Transaction 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)
|
|
KexiDB::DatabaseProperties &props = d->connection->databaseProperties();
|
|
if (!props.setValue("kexiproject_major_ver", d->versionMajor)
|
|
|| !props.setCaption("kexiproject_major_ver", i18n("Project major version"))
|
|
|| !props.setValue("kexiproject_minor_ver", d->versionMinor)
|
|
|| !props.setCaption("kexiproject_minor_ver", i18n("Project minor version"))
|
|
|| !props.setValue("project_caption", d->data->caption())
|
|
|| !props.setCaption("project_caption", i18n("Project caption"))
|
|
|| !props.setValue("project_desc", d->data->description())
|
|
|| !props.setCaption("project_desc", i18n("Project description")) )
|
|
return false;
|
|
|
|
/* KexiDB::TableSchema *t_db = d->connection->tableSchema("kexi__db");
|
|
//caption:
|
|
if (!t_db)
|
|
return false;
|
|
|
|
if (!KexiDB::replaceRow(*d->connection, t_db, "db_property", "project_caption",
|
|
"db_value", TQVariant( d->data->caption() ), KexiDB::Field::Text)
|
|
|| !KexiDB::replaceRow(*d->connection, t_db, "db_property", "project_desc",
|
|
"db_value", TQVariant( d->data->description() ), KexiDB::Field::Text) )
|
|
return false;
|
|
*/
|
|
if (trans.active() && !d->connection->commitTransaction(trans))
|
|
return false;
|
|
//</add some data>
|
|
|
|
return initProject();
|
|
}
|
|
|
|
bool KexiProject::createInternalStructures(bool insideTransaction)
|
|
{
|
|
KexiDB::TransactionGuard 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.
|
|
KexiDB::DatabaseProperties &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;
|
|
|
|
bool containsKexi__blobsTable = d->connection->drv_containsTable("kexi__blobs");
|
|
int dummy;
|
|
bool contains_o_folder_id = containsKexi__blobsTable && true == d->connection->querySingleNumber(
|
|
"SELECT COUNT(o_folder_id) FROM kexi__blobs", dummy, 0, false/*addLimitTo1*/);
|
|
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->isReadOnly()) {
|
|
if (!props.setValue("kexiproject_major_ver", d->versionMajor)
|
|
|| !props.setCaption("kexiproject_major_ver", i18n("Project major version"))
|
|
|| !props.setValue("kexiproject_minor_ver", d->versionMinor)
|
|
|| !props.setCaption("kexiproject_minor_ver", i18n("Project minor version")) ) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (containsKexi__blobsTable) {
|
|
//! @todo what to do for readonly connections? Should we alter kexi__blobs in memory?
|
|
if (!d->connection->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;
|
|
}
|
|
|
|
KexiDB::InternalTableSchema *t_blobs = new KexiDB::InternalTableSchema("kexi__blobs");
|
|
t_blobs->addField( new KexiDB::Field("o_id", KexiDB::Field::Integer,
|
|
KexiDB::Field::PrimaryKey | KexiDB::Field::AutoInc, KexiDB::Field::Unsigned) )
|
|
.addField( new KexiDB::Field("o_data", KexiDB::Field::BLOB) )
|
|
.addField( new KexiDB::Field("o_name", KexiDB::Field::Text ) )
|
|
.addField( new KexiDB::Field("o_caption", KexiDB::Field::Text ) )
|
|
.addField( new KexiDB::Field("o_mime", KexiDB::Field::Text, KexiDB::Field::NotNull) )
|
|
.addField( new KexiDB::Field("o_folder_id",
|
|
KexiDB::Field::Integer, 0, KexiDB::Field::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 (containsKexi__blobsTable) {
|
|
//! just insert this schema
|
|
d->connection->insertInternalTableSchema(t_blobs);
|
|
if (add_folder_id_column && !d->connection->isReadOnly()) {
|
|
// 2. "kexi__blobs" table contains no "o_folder_id" column -> add it
|
|
// (by copying table to avoid data loss)
|
|
KexiDB::TableSchema *kexi__blobsCopy = new KexiDB::TableSchema( *t_blobs );
|
|
kexi__blobsCopy->setName("kexi__blobs__copy");
|
|
if (!d->connection->drv_createTable( *kexi__blobsCopy )) {
|
|
delete kexi__blobsCopy;
|
|
delete t_blobs;
|
|
return false;
|
|
}
|
|
// 2.1 copy data (insert 0's into o_folder_id column)
|
|
if (!d->connection->executeSQL(
|
|
TQString::fromLatin1("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(TQString::fromLatin1("DROP TABLE kexi__blobs")) //lowlevel
|
|
// 2.3 rename the copy back into kexi__blobs
|
|
|| !d->connection->drv_alterTableName(*kexi__blobsCopy, "kexi__blobs")
|
|
)
|
|
{
|
|
//(no need to drop the copy, ROLLBACK will drop it)
|
|
delete kexi__blobsCopy;
|
|
delete t_blobs;
|
|
return false;
|
|
}
|
|
delete kexi__blobsCopy; //not needed - physically renamed to kexi_blobs
|
|
}
|
|
}
|
|
else {
|
|
// if (!d->connection->createTable( t_blobs, false/*!replaceExisting*/ )) {
|
|
if (!d->connection->isReadOnly()) {
|
|
if (!d->connection->createTable( t_blobs, true/*replaceExisting*/ )) {
|
|
delete t_blobs;
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
//Store default part infos.
|
|
//Infos for other parts (forms, reports...) are created on demand in KexiDialogBase::storeNewData()
|
|
KexiDB::InternalTableSchema *t_parts = new KexiDB::InternalTableSchema("kexi__parts"); //newKexiDBSystemTableSchema("kexi__parts");
|
|
t_parts->addField(
|
|
new KexiDB::Field("p_id", KexiDB::Field::Integer, KexiDB::Field::PrimaryKey | KexiDB::Field::AutoInc, KexiDB::Field::Unsigned)
|
|
)
|
|
.addField( new KexiDB::Field("p_name", KexiDB::Field::Text) )
|
|
.addField( new KexiDB::Field("p_mime", KexiDB::Field::Text ) )
|
|
.addField( new KexiDB::Field("p_url", KexiDB::Field::Text ) );
|
|
|
|
bool containsKexi__partsTable = d->connection->drv_containsTable("kexi__parts");
|
|
bool partsTableOk = true;
|
|
if (containsKexi__partsTable) {
|
|
//! just insert this schema
|
|
d->connection->insertInternalTableSchema(t_parts);
|
|
}
|
|
else {
|
|
if (!d->connection->isReadOnly()) {
|
|
partsTableOk = d->connection->createTable( t_parts, true/*replaceExisting*/ );
|
|
|
|
KexiDB::FieldList *fl = t_parts->subList("p_id", "p_name", "p_mime", "p_url");
|
|
if (partsTableOk)
|
|
partsTableOk = d->connection->insertRecord(*fl, TQVariant(1), TQVariant("Tables"),
|
|
TQVariant("kexi/table"), TQVariant("http://koffice.org/kexi/"));
|
|
|
|
if (partsTableOk)
|
|
partsTableOk = d->connection->insertRecord(*fl, TQVariant(2), TQVariant("Queries"),
|
|
TQVariant("kexi/query"), TQVariant("http://koffice.org/kexi/"));
|
|
}
|
|
}
|
|
|
|
if (!partsTableOk) {
|
|
delete t_parts;
|
|
return false;
|
|
}
|
|
|
|
if (insideTransaction) {
|
|
if (tg.transaction().active() && !tg.commit())
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
KexiProject::createConnection()
|
|
{
|
|
if (d->connection)
|
|
return true;
|
|
|
|
clearError();
|
|
// closeConnection();//for sanity
|
|
KexiDB::MessageTitle et(this);
|
|
|
|
KexiDB::Driver *driver = Kexi::driverManager().driver(d->data->connectionData()->driverName);
|
|
if(!driver) {
|
|
setError(&Kexi::driverManager());
|
|
return false;
|
|
}
|
|
|
|
int connectionOptions = 0;
|
|
if (d->data->isReadOnly())
|
|
connectionOptions |= KexiDB::Driver::ReadOnlyConnection;
|
|
d->connection = driver->createConnection(*d->data->connectionData(), connectionOptions);
|
|
if (!d->connection)
|
|
{
|
|
kdDebug() << "KexiProject::open(): uuups failed " << driver->errorMsg() << endl;
|
|
setError(driver);
|
|
return false;
|
|
}
|
|
|
|
if (!d->connection->connect())
|
|
{
|
|
setError(d->connection);
|
|
kdDebug() << "KexiProject::createConnection(): error connecting: " << (d->connection ? d->connection->errorMsg() : TQString()) << endl;
|
|
closeConnection();
|
|
return false;
|
|
}
|
|
|
|
//re-init BLOB buffer
|
|
//! @todo won't work for subsequent connection
|
|
KexiBLOBBuffer::setConnection(d->connection);
|
|
return true;
|
|
}
|
|
|
|
|
|
bool
|
|
KexiProject::closeConnection()
|
|
{
|
|
if (!d->connection)
|
|
return true;
|
|
|
|
if (!d->connection->disconnect()) {
|
|
setError(d->connection);
|
|
return false;
|
|
}
|
|
|
|
delete d->connection; //this will also clear connection for BLOB buffer
|
|
d->connection = 0;
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
KexiProject::initProject()
|
|
{
|
|
// emit dbAvailable();
|
|
kdDebug() << "KexiProject::open(): checking project parts..." << endl;
|
|
|
|
if (!Kexi::partManager().checkProject(d->connection)) {
|
|
setError(Kexi::partManager().error() ? (KexiDB::Object*)&Kexi::partManager() : (KexiDB::Connection*)d->connection);
|
|
return false;
|
|
}
|
|
|
|
// !@todo put more props. todo - creator, created date, etc. (also to KexiProjectData)
|
|
KexiDB::DatabaseProperties &props = d->connection->databaseProperties();
|
|
TQString 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 );
|
|
/* KexiDB::RowData data;
|
|
TQString sql = "select db_value from kexi__db where db_property='%1'";
|
|
if (d->connection->querySingleRecord( sql.arg("project_caption"), data ) && !data[0].toString().isEmpty())
|
|
d->data->setCaption(data[0].toString());
|
|
if (d->connection->querySingleRecord( sql.arg("project_desc"), data) && !data[0].toString().isEmpty())
|
|
d->data->setDescription(data[0].toString());*/
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
KexiProject::isConnected()
|
|
{
|
|
if(d->connection && d->connection->isDatabaseUsed())
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
KexiPart::ItemDict*
|
|
KexiProject::items(KexiPart::Info *i)
|
|
{
|
|
kdDebug() << "KexiProject::items()" << endl;
|
|
if(!i || !isConnected())
|
|
return 0;
|
|
|
|
//trying in cache...
|
|
KexiPart::ItemDict *dict = d->itemDictsCache[ i->projectPartID() ];
|
|
if (dict)
|
|
return dict;
|
|
//retrieve:
|
|
KexiDB::Cursor *cursor = d->connection->executeQuery(
|
|
"SELECT o_id, o_name, o_caption FROM kexi__objects WHERE o_type = "
|
|
+ TQString::number(i->projectPartID()));//, KexiDB::Cursor::Buffered);
|
|
// kdDebug() << "KexiProject::items(): cursor handle is:" << cursor << endl;
|
|
if(!cursor)
|
|
return 0;
|
|
|
|
dict = new KexiPart::ItemDict(1009);
|
|
dict->setAutoDelete(true);
|
|
|
|
for(cursor->moveFirst(); !cursor->eof(); cursor->moveNext())
|
|
{
|
|
KexiPart::Item *it = new KexiPart::Item();
|
|
bool ok;
|
|
int ident = cursor->value(0).toInt(&ok);
|
|
TQString objName( cursor->value(1).toString() );
|
|
|
|
if ( ok && (ident>0) && !d->connection->isInternalTableSchema(objName)
|
|
&& KexiUtils::isIdentifier(objName) )
|
|
{
|
|
it->setIdentifier(ident);
|
|
it->setMimeType(i->mimeType()); //js: may be not null???
|
|
it->setName(objName);
|
|
it->setCaption(cursor->value(2).toString());
|
|
}
|
|
dict->insert(it->identifier(), it);
|
|
// kdDebug() << "KexiProject::items(): ITEM ADDED == "<<objName <<" id="<<ident<<endl;
|
|
}
|
|
|
|
d->connection->deleteCursor(cursor);
|
|
// kdDebug() << "KexiProject::items(): end with count " << dict->count() << endl;
|
|
d->itemDictsCache.insert( i->projectPartID(), dict );
|
|
return dict;
|
|
}
|
|
|
|
KexiPart::ItemDict*
|
|
KexiProject::itemsForMimeType(const TQCString &mimeType)
|
|
{
|
|
KexiPart::Info *info = Kexi::partManager().infoForMimeType(mimeType);
|
|
return items(info);
|
|
}
|
|
|
|
void
|
|
KexiProject::getSortedItems(KexiPart::ItemList& list, KexiPart::Info *i)
|
|
{
|
|
list.clear();
|
|
KexiPart::ItemDict* dict = items(i);
|
|
if (!dict)
|
|
return;
|
|
for (KexiPart::ItemDictIterator it(*dict); it.current(); ++it)
|
|
list.append(it.current());
|
|
}
|
|
|
|
void
|
|
KexiProject::getSortedItemsForMimeType(KexiPart::ItemList& list, const TQCString &mimeType)
|
|
{
|
|
KexiPart::Info *info = Kexi::partManager().infoForMimeType(mimeType);
|
|
getSortedItems(list, info);
|
|
}
|
|
|
|
void
|
|
KexiProject::addStoredItem(KexiPart::Info *info, KexiPart::Item *item)
|
|
{
|
|
if (!info || !item)
|
|
return;
|
|
KexiPart::ItemDict *dict = items(info);
|
|
item->setNeverSaved( false );
|
|
d->unstoredItems.take(item); //no longer unstored
|
|
dict->insert( item->identifier(), item );
|
|
//let's update e.g. navigator
|
|
emit newItemStored(*item);
|
|
}
|
|
|
|
KexiPart::Item*
|
|
KexiProject::itemForMimeType(const TQCString &mimeType, const TQString &name)
|
|
{
|
|
KexiPart::ItemDict *dict = itemsForMimeType(mimeType);
|
|
if (!dict)
|
|
return 0;
|
|
const TQString l_name = name.lower();
|
|
for (KexiPart::ItemDictIterator it( *dict ); it.current(); ++it) {
|
|
if (it.current()->name().lower()==l_name)
|
|
return it.current();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
KexiPart::Item*
|
|
KexiProject::item(KexiPart::Info *i, const TQString &name)
|
|
{
|
|
KexiPart::ItemDict *dict = items(i);
|
|
if (!dict)
|
|
return 0;
|
|
const TQString l_name = name.lower();
|
|
for (KexiPart::ItemDictIterator it( *dict ); it.current(); ++it) {
|
|
if (it.current()->name().lower()==l_name)
|
|
return it.current();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
KexiPart::Item*
|
|
KexiProject::item(int identifier)
|
|
{
|
|
KexiPart::ItemDict *dict;
|
|
for (TQIntDictIterator<KexiPart::ItemDict> it(d->itemDictsCache); (dict = it.current()); ++it) {
|
|
KexiPart::Item *item = dict->find(identifier);
|
|
if (item)
|
|
return item;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*void KexiProject::clearMsg()
|
|
{
|
|
clearError();
|
|
// d->error_title=TQString();
|
|
}
|
|
|
|
void KexiProject::setError(int code, const TQString &msg )
|
|
{
|
|
Object::setError(code, msg);
|
|
if (Object::error())
|
|
ERRMSG(d->error_title, this);
|
|
// emit error(d->error_title, this);
|
|
}
|
|
|
|
|
|
void KexiProject::setError( const TQString &msg )
|
|
{
|
|
Object::setError(msg);
|
|
if (Object::error())
|
|
ERRMSG(d->error_title, this);
|
|
// emit error(d->error_title, this);
|
|
}
|
|
|
|
void KexiProject::setError( KexiDB::Object *obj )
|
|
{
|
|
if (!obj)
|
|
return;
|
|
Object::setError(obj);
|
|
if (Object::error())
|
|
ERRMSG(d->error_title, obj);
|
|
// emit error(d->error_title, obj);
|
|
}
|
|
|
|
void KexiProject::setError(const TQString &msg, const TQString &desc)
|
|
{
|
|
Object::setError(msg); //ok?
|
|
ERRMSG(msg, desc); //not KexiDB-related
|
|
// emit error(msg, desc); //not KexiDB-related
|
|
}
|
|
*/
|
|
|
|
KexiPart::Part *KexiProject::findPartFor(KexiPart::Item& item)
|
|
{
|
|
clearError();
|
|
KexiDB::MessageTitle et(this);
|
|
KexiPart::Part *part = Kexi::partManager().partForMimeType(item.mimeType());
|
|
if (!part)
|
|
setError(&Kexi::partManager());
|
|
return part;
|
|
}
|
|
|
|
KexiDialogBase* KexiProject::openObject(KexiMainWindow *wnd, KexiPart::Item& item,
|
|
int viewMode, TQMap<TQString,TQString>* staticObjectArgs)
|
|
{
|
|
clearError();
|
|
if (viewMode!=Kexi::DataViewMode && data()->userMode())
|
|
return 0;
|
|
|
|
KexiDB::MessageTitle et(this);
|
|
KexiPart::Part *part = findPartFor(item);
|
|
if (!part)
|
|
return 0;
|
|
KexiDialogBase *dlg = part->openInstance(wnd, item, viewMode, staticObjectArgs);
|
|
if (!dlg) {
|
|
if (part->lastOperationStatus().error())
|
|
setError(i18n("Opening object \"%1\" failed.").arg(item.name())+"<br>"
|
|
+part->lastOperationStatus().message,
|
|
part->lastOperationStatus().description);
|
|
return 0;
|
|
}
|
|
return dlg;
|
|
}
|
|
|
|
KexiDialogBase* KexiProject::openObject(KexiMainWindow *wnd, const TQCString &mimeType,
|
|
const TQString& name, int viewMode)
|
|
{
|
|
KexiPart::Item *it = itemForMimeType(mimeType, name);
|
|
return it ? openObject(wnd, *it, viewMode) : 0;
|
|
}
|
|
|
|
bool KexiProject::checkWritable()
|
|
{
|
|
if (!d->connection->isReadOnly())
|
|
return true;
|
|
setError(i18n("This project is opened as read only."));
|
|
return false;
|
|
}
|
|
|
|
bool KexiProject::removeObject(KexiMainWindow *wnd, KexiPart::Item& item)
|
|
{
|
|
clearError();
|
|
if (data()->userMode())
|
|
return false;
|
|
|
|
KexiDB::MessageTitle et(this);
|
|
if (!checkWritable())
|
|
return false;
|
|
KexiPart::Part *part = findPartFor(item);
|
|
if (!part)
|
|
return false;
|
|
if (!item.neverSaved() && !part->remove(wnd, item)) {
|
|
//js TODO check for errors
|
|
return false;
|
|
}
|
|
if (!item.neverSaved()) {
|
|
KexiDB::TransactionGuard tg( *d->connection );
|
|
if (!tg.transaction().active()) {
|
|
setError(d->connection);
|
|
return false;
|
|
}
|
|
if (!d->connection->removeObject( item.identifier() )) {
|
|
setError(d->connection);
|
|
return false;
|
|
}
|
|
if (!tg.commit()) {
|
|
setError(d->connection);
|
|
return false;
|
|
}
|
|
}
|
|
emit itemRemoved(item);
|
|
|
|
//now: remove this item from cache
|
|
if (part->info()) {
|
|
KexiPart::ItemDict *dict = d->itemDictsCache[ part->info()->projectPartID() ];
|
|
if (!(dict && dict->remove( item.identifier() )))
|
|
d->unstoredItems.remove(&item);//remove temp.
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool KexiProject::renameObject( KexiMainWindow *wnd, KexiPart::Item& item, const TQString& _newName )
|
|
{
|
|
clearError();
|
|
if (data()->userMode())
|
|
return 0;
|
|
|
|
KexiUtils::WaitCursor wait;
|
|
TQString newName = _newName.stripWhiteSpace();
|
|
{
|
|
KexiDB::MessageTitle et(this);
|
|
if (newName.isEmpty()) {
|
|
setError( i18n("Could not set empty name for this object.") );
|
|
return false;
|
|
}
|
|
if (this->itemForMimeType(item.mimeType(), newName)!=0) {
|
|
setError( i18n("Could not use this name. Object with name \"%1\" already exists.")
|
|
.arg(newName) );
|
|
return false;
|
|
}
|
|
}
|
|
|
|
KexiDB::MessageTitle et(this,
|
|
i18n("Could not rename object \"%1\".").arg(item.name()) );
|
|
if (!checkWritable())
|
|
return false;
|
|
KexiPart::Part *part = findPartFor(item);
|
|
if (!part)
|
|
return false;
|
|
KexiDB::TransactionGuard tg( *d->connection );
|
|
if (!tg.transaction().active()) {
|
|
setError(d->connection);
|
|
return false;
|
|
}
|
|
if (!part->rename(wnd, item, newName)) {
|
|
setError(part->lastOperationStatus().message, part->lastOperationStatus().description);
|
|
return false;
|
|
}
|
|
if (!d->connection->executeSQL( "update kexi__objects set o_name="
|
|
+ d->connection->driver()->valueToSQL( KexiDB::Field::Text, newName )
|
|
+ " where o_id=" + TQString::number(item.identifier()) )) {
|
|
setError(d->connection);
|
|
return false;
|
|
}
|
|
if (!tg.commit()) {
|
|
setError(d->connection);
|
|
return false;
|
|
}
|
|
TQCString oldName( item.name().latin1() );
|
|
item.setName( newName );
|
|
emit itemRenamed(item, oldName);
|
|
return true;
|
|
}
|
|
|
|
KexiPart::Item* KexiProject::createPartItem(KexiPart::Info *info, const TQString& suggestedCaption)
|
|
{
|
|
clearError();
|
|
if (data()->userMode())
|
|
return 0;
|
|
|
|
KexiDB::MessageTitle et(this);
|
|
KexiPart::Part *part = Kexi::partManager().part(info);
|
|
if (!part) {
|
|
setError(&Kexi::partManager());
|
|
return 0;
|
|
}
|
|
|
|
KexiPart::ItemDict *dict = items(info);
|
|
|
|
//find new, unique default name for this item
|
|
int n;
|
|
TQString new_name;
|
|
TQString base_name;
|
|
if (suggestedCaption.isEmpty()) {
|
|
n = 1;
|
|
base_name = part->instanceName();
|
|
}
|
|
else {
|
|
n = 0; //means: try not to add 'n'
|
|
base_name = KexiUtils::string2Identifier(suggestedCaption).lower();
|
|
}
|
|
base_name = KexiUtils::string2Identifier(base_name).lower();
|
|
KexiPart::ItemDictIterator it(*dict);
|
|
TQPtrDictIterator<KexiPart::Item> itUnstored(d->unstoredItems);
|
|
do {
|
|
new_name = base_name;
|
|
if (n>=1)
|
|
new_name += TQString::number(n);
|
|
for (it.toFirst(); it.current(); ++it) {
|
|
if (it.current()->name().lower()==new_name)
|
|
break;
|
|
}
|
|
if ( it.current() ) {
|
|
n++;
|
|
continue; //stored exists!
|
|
}
|
|
for (itUnstored.toFirst(); itUnstored.current(); ++itUnstored) {
|
|
if (itUnstored.current()->name().lower()==new_name)
|
|
break;
|
|
}
|
|
if ( !itUnstored.current() )
|
|
break; //unstored doesn't exist
|
|
n++;
|
|
} while (n<1000/*sanity*/);
|
|
|
|
if (n>=1000)
|
|
return 0;
|
|
|
|
TQString new_caption( suggestedCaption.isEmpty() ? part->instanceCaption() : suggestedCaption);
|
|
if (n>=1)
|
|
new_caption += TQString::number(n);
|
|
|
|
KexiPart::Item *item = new KexiPart::Item();
|
|
item->setIdentifier( --d->tempPartItemID_Counter );//temporary
|
|
item->setMimeType(info->mimeType());
|
|
item->setName(new_name);
|
|
item->setCaption(new_caption);
|
|
item->setNeverSaved(true);
|
|
d->unstoredItems.insert(item, item);
|
|
return item;
|
|
}
|
|
|
|
KexiPart::Item* KexiProject::createPartItem(KexiPart::Part *part, const TQString& suggestedCaption)
|
|
{
|
|
return createPartItem(part->info(), suggestedCaption);
|
|
}
|
|
|
|
void KexiProject::deleteUnstoredItem(KexiPart::Item *item)
|
|
{
|
|
if (!item)
|
|
return;
|
|
d->unstoredItems.remove(item);
|
|
}
|
|
|
|
KexiDB::Parser* KexiProject::sqlParser()
|
|
{
|
|
if (!d->sqlParser) {
|
|
if (!d->connection)
|
|
return 0;
|
|
d->sqlParser = new KexiDB::Parser(d->connection);
|
|
}
|
|
return d->sqlParser;
|
|
}
|
|
|
|
static const TQString warningNoUndo = i18n("Warning: entire project's data will be removed.");
|
|
|
|
/*static*/
|
|
KexiProject*
|
|
KexiProject::createBlankProject(bool &cancelled, KexiProjectData* data,
|
|
KexiDB::MessageHandler* handler)
|
|
{
|
|
cancelled = false;
|
|
KexiProject *prj = new KexiProject( new KexiProjectData(*data), handler );
|
|
|
|
tristate res = prj->create(false);
|
|
if (~res) {
|
|
//! @todo move to KexiMessageHandler
|
|
if (KMessageBox::Yes != KMessageBox::warningYesNo(0, "<qt>"+i18n(
|
|
"The project %1 already exists.\n"
|
|
"Do you want to replace it with a new, blank one?")
|
|
.arg(prj->data()->infoString())+"\n"+warningNoUndo+"</qt>",
|
|
TQString(), KGuiItem(i18n("Replace")), KStdGuiItem::cancel() ))
|
|
//todo add serverInfoString() for server-based prj
|
|
{
|
|
delete prj;
|
|
cancelled = true;
|
|
return 0;
|
|
}
|
|
res = prj->create(true/*overwrite*/);
|
|
}
|
|
if (res != true) {
|
|
delete prj;
|
|
return 0;
|
|
}
|
|
kdDebug() << "KexiProject::createBlankProject(): new project created --- " << endl;
|
|
//todo? Kexi::recentProjects().addProjectData( data );
|
|
|
|
return prj;
|
|
}
|
|
|
|
/*static*/
|
|
tristate KexiProject::dropProject(KexiProjectData* data,
|
|
KexiDB::MessageHandler* handler, bool dontAsk)
|
|
{
|
|
if (!dontAsk && KMessageBox::Yes != KMessageBox::warningYesNo(0,
|
|
i18n("Do you want to drop the project \"%1\"?").arg(data->KexiDB::SchemaData::objectName())+"\n"+warningNoUndo ))
|
|
return cancelled;
|
|
|
|
KexiProject prj( new KexiProjectData(*data), handler );
|
|
if (!prj.open())
|
|
return false;
|
|
|
|
if (prj.dbConnection()->isReadOnly()) {
|
|
handler->showErrorMessage(
|
|
i18n("Could not drop this project. Database connection for this project has been opened as read only."));
|
|
return false;
|
|
}
|
|
|
|
return prj.dbConnection()->dropDatabase();
|
|
}
|
|
|
|
/*void KexiProject::reloadPartItem( KexiDialogBase* dialog )
|
|
{
|
|
if (!dialog)
|
|
return;
|
|
|
|
KexiPart::Item* item = dialog->partItem();
|
|
|
|
if (dialog || !d->connection->setQuerySchemaObsolete( queryName ))
|
|
return;
|
|
KexiPart::Info *partInfo = Kexi::partManager().infoForMimeType("kexi/query");
|
|
if (!partInfo)
|
|
return; //err?
|
|
item(partInfo, queryName);
|
|
if (!item)
|
|
return; //err?
|
|
emit itemSetO
|
|
|
|
}*/
|
|
|
|
#include "kexiproject.moc"
|