diff --git a/src/plugins/forms/kexidatasourcepage.h b/src/plugins/forms/kexidatasourcepage.h --- a/src/plugins/forms/kexidatasourcepage.h +++ b/src/plugins/forms/kexidatasourcepage.h @@ -1,5 +1,5 @@ /* This file is part of the KDE project - Copyright (C) 2005-2009 Jarosław Staniek + Copyright (C) 2005-2017 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 @@ -45,6 +45,12 @@ explicit KexiDataSourcePage(QWidget *parent); virtual ~KexiDataSourcePage(); + //! @return name plugin ID of selected item (usually a table or a query). Can return an empty string. + QString selectedPluginId() const; + + //! @return name of selected table or query. + QString selectedName() const; + public Q_SLOTS: void setProject(KexiProject *prj); void clearFormDataSourceSelection(bool alsoClearComboBox = true); diff --git a/src/plugins/forms/kexidatasourcepage.cpp b/src/plugins/forms/kexidatasourcepage.cpp --- a/src/plugins/forms/kexidatasourcepage.cpp +++ b/src/plugins/forms/kexidatasourcepage.cpp @@ -1,5 +1,5 @@ /* This file is part of the KDE project - Copyright (C) 2005-2009 Jarosław Staniek + Copyright (C) 2005-2017 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 @@ -452,3 +452,12 @@ #endif } +QString KexiDataSourcePage::selectedPluginId() const +{ + return m_formDataSourceCombo->selectedPluginId(); +} + +QString KexiDataSourcePage::selectedName() const +{ + return m_formDataSourceCombo->selectedName(); +} diff --git a/src/plugins/forms/kexiformpart.h b/src/plugins/forms/kexiformpart.h --- a/src/plugins/forms/kexiformpart.h +++ b/src/plugins/forms/kexiformpart.h @@ -1,7 +1,7 @@ /* This file is part of the KDE project Copyright (C) 2004 Lucijan Busch Copyright (C) 2004 Cedric Pasteur - Copyright (C) 2005-2009 Jarosław Staniek + Copyright (C) 2005-2017 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 @@ -28,6 +28,8 @@ #include #include +#include + class QDomDocument; namespace KFormDesigner { @@ -37,12 +39,18 @@ class KDbFieldList; class KexiDataSourcePage; -class KexiFormPartTempData : public KexiWindowData +class KexiFormPartTempData : public KexiWindowData, public KDbTableSchemaChangeListener { Q_OBJECT public: - explicit KexiFormPartTempData(KexiWindow* parent); + KexiFormPartTempData(KexiWindow* parent, KDbConnection *conn); ~KexiFormPartTempData(); + + //! Sets data source used for this data. + //! If the previous data source is different and is not empty, listener for it will be unregistered. + //! If the new data source is empty this temp-data object will be registered as a listener for it. + void setDataSource(const QString &pluginId, const QString &dataSource); + QPointer form; QPointer previewForm; QString tempForm; @@ -52,6 +60,16 @@ //! Used when loading a form from (temporary) XML in Data View //! to get unsaved blobs collected at design mode. QHash unsavedLocalBLOBsByName; + +protected: + //! This temp-data acts as a listener for tracking changes in table schema + //! used by the form. This method closes the form on request. + tristate closeListener() override; + +private: + Q_DISABLE_COPY(KexiFormPartTempData) + class Private; + Private * const d; }; //! Kexi Form Plugin diff --git a/src/plugins/forms/kexiformpart.cpp b/src/plugins/forms/kexiformpart.cpp --- a/src/plugins/forms/kexiformpart.cpp +++ b/src/plugins/forms/kexiformpart.cpp @@ -1,7 +1,7 @@ /* This file is part of the KDE project Copyright (C) 2004 Lucijan Busch Copyright (C) 2004 Cedric Pasteur - Copyright (C) 2005 Jarosław Staniek + Copyright (C) 2005-2017 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 @@ -147,7 +147,8 @@ KexiWindowData* KexiFormPart::createWindowData(KexiWindow* window) { - return new KexiFormPartTempData(window); + KexiMainWindowIface *win = KexiMainWindowIface::global(); + return new KexiFormPartTempData(window, win->project()->dbConnection()); } KexiView* KexiFormPart::createView(QWidget *parent, KexiWindow* window, @@ -378,12 +379,90 @@ //---------------- -KexiFormPartTempData::KexiFormPartTempData(KexiWindow* parent) - : KexiWindowData(parent) +class Q_DECL_HIDDEN KexiFormPartTempData::Private { +public: + Private(KexiFormPartTempData *temp) + : q(temp) + { + } + + void unregisterForChanges() + { + if (dataSource.isEmpty()) { + return; + } + if (pluginId == "org.kexi-project.table") { + KDbTableSchema *table = conn->tableSchema(dataSource); + if (!table) { + return; + } + KDbTableSchemaChangeListener::unregisterForChanges(conn, table); + } else if (pluginId == "org.kexi-project.query") { + KDbQuerySchema *query = conn->querySchema(dataSource); + if (!query) { + return; + } + KDbTableSchemaChangeListener::unregisterForChanges(conn, query); + } else { + return; + } + } + + void registerForChanges(const QString &newPluginId, const QString &newDataSource) + { + if (newPluginId == "org.kexi-project.table") { + KDbTableSchema *table = conn->tableSchema(newDataSource); + if (!table) { + return; + } + KDbTableSchemaChangeListener::registerForChanges(conn, q, table); + } else if (pluginId == "org.kexi-project.query") { + KDbQuerySchema *query = conn->querySchema(newDataSource); + if (!query) { + return; + } + KDbTableSchemaChangeListener::registerForChanges(conn, q, query); + } else { + return; + } + pluginId = newPluginId; + dataSource = newDataSource; + } + + KDbConnection *conn; + QString pluginId; + QString dataSource; + +private: + KexiFormPartTempData * const q; +}; + +KexiFormPartTempData::KexiFormPartTempData(KexiWindow* parent, KDbConnection *conn) + : KexiWindowData(parent), d(new Private(this)) +{ + d->conn = conn; + setName(KexiUtils::localizedStringToHtmlSubstring( + kxi18nc("@info", "Form %1").subs(parent->partItem()->name()))); } KexiFormPartTempData::~KexiFormPartTempData() { + KDbTableSchemaChangeListener::unregisterForChanges(d->conn, this); + delete d; } +void KexiFormPartTempData::setDataSource(const QString &pluginId, const QString &dataSource) +{ + if (d->pluginId != pluginId || d->dataSource != dataSource) { + d->unregisterForChanges(); + d->registerForChanges(pluginId, dataSource); + } +} + +tristate KexiFormPartTempData::closeListener() +{ + KexiWindow* window = static_cast(parent()); + qDebug() << window->partItem()->name(); + return KexiMainWindowIface::global()->closeWindow(window); +} diff --git a/src/plugins/forms/kexiformview.h b/src/plugins/forms/kexiformview.h --- a/src/plugins/forms/kexiformview.h +++ b/src/plugins/forms/kexiformview.h @@ -1,6 +1,6 @@ /* This file is part of the KDE project Copyright (C) 2004 Cedric Pasteur - Copyright (C) 2004-2016 Jarosław Staniek + Copyright (C) 2004-2017 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 diff --git a/src/plugins/forms/kexiformview.cpp b/src/plugins/forms/kexiformview.cpp --- a/src/plugins/forms/kexiformview.cpp +++ b/src/plugins/forms/kexiformview.cpp @@ -1,6 +1,6 @@ /* This file is part of the KDE project Copyright (C) 2004 Cedric Pasteur - Copyright (C) 2004-2016 Jarosław Staniek + Copyright (C) 2004-2017 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 @@ -458,6 +458,7 @@ if (!KFormDesigner::FormIO::loadFormFromString(form(), d->dbform, data)) { return false; } + tempData()->setDataSource(d->dbform->dataSourcePluginId(), d->dbform->dataSource()); } //"autoTabStops" property is loaded -set it within the form tree as well @@ -1064,9 +1065,16 @@ { if (viewMode() == Kexi::DesignViewMode) { KPropertySet *set = form()->propertySet(); - const QString dataSourcePartClass = set->propertyValue("dataSourcePartClass").toString(); + QString dataSourcePartClass = set->propertyValue("dataSourcePartClass").toString(); const QString dataSource = set->propertyValue("dataSource").toString(); formPart()->dataSourcePage()->setFormDataSource(dataSourcePartClass, dataSource); + if (dataSourcePartClass.isEmpty() + && !formPart()->dataSourcePage()->selectedPluginId().isEmpty()) + { + set->property("dataSourcePartClass") + .setValue(formPart()->dataSourcePage()->selectedPluginId(), + KProperty::ValueOption::IgnoreOld); + } } } diff --git a/src/plugins/queries/kexiquerypart.h b/src/plugins/queries/kexiquerypart.h --- a/src/plugins/queries/kexiquerypart.h +++ b/src/plugins/queries/kexiquerypart.h @@ -1,6 +1,6 @@ /* This file is part of the KDE project Copyright (C) 2003 Lucijan Busch - Copyright (C) 2004-2016 Jarosław Staniek + Copyright (C) 2004-2017 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 @@ -101,6 +101,38 @@ Reimplemented to mark the query obsolete by using KDbConnection::setQuerySchemaObsolete(). */ virtual tristate rename(KexiPart::Item *item, const QString& newName); + /** + * Closes objects that listenen to changes of the query schema @a query, i.e. use it. + * + * These objects can be currently: + * - lookup fields of tables + * - queries using the query directly (as subqueries) or via lookup fields + * - forms and reports that use the query directly as data source or via query. + * + * Scripts referencing the query programatically are not analyzed, so they can fail on next + * execution. + * + * This method asks the user for approval if there is at least one object that listens for + * changes of the schema (altering or removal). If there is no approval, returns + * @c cancelled. On failure @c false is returned. If @a window is @c nullptr, @c true is + * returned immediately because there is no window to care about. + * + * @note Unlike renaming tables, renaming queries just marks the previous query object one as + * "obsolete" using KDbConnection::setQuerySchemaObsolete() and keeps the existing actual object + * in memory so there is no risk of accessing deleted object by other objects. + * + * Special case: listener for the query @a query will be silently closed without asking for + * confirmation. It is ignored when looking for objects that are "blocking" changes + * of @a query. This exception is needed because the listener handles the data view's lifetime + * and the data view should be reset silently without bothering the user. + * + * @see KexiQueryPartTempData::closeListener() + * @see KexiTablePart::askForClosingObjectsUsingTableSchema() + */ + static tristate askForClosingObjectsUsingQuerySchema(KexiWindow *window, KDbConnection *conn, + KDbQuerySchema *query, + const KLocalizedString &msg); + protected: KexiWindowData* createWindowData(KexiWindow* window) override Q_REQUIRED_RESULT; diff --git a/src/plugins/queries/kexiquerypart.cpp b/src/plugins/queries/kexiquerypart.cpp --- a/src/plugins/queries/kexiquerypart.cpp +++ b/src/plugins/queries/kexiquerypart.cpp @@ -1,6 +1,6 @@ /* This file is part of the KDE project Copyright (C) 2004 Lucijan Busch - Copyright (C) 2004-2016 Jarosław Staniek + Copyright (C) 2004-2017 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 @@ -33,6 +33,9 @@ #include #include +#include +#include + #include KEXI_PLUGIN_FACTORY(KexiQueryPart, "kexi_queryplugin.json") @@ -99,8 +102,17 @@ return false; KDbConnection *conn = KexiMainWindowIface::global()->project()->dbConnection(); KDbQuerySchema *sch = conn->querySchema(item->identifier()); - if (sch) + if (sch) { + const tristate res = KexiQueryPart::askForClosingObjectsUsingQuerySchema( + KexiMainWindowIface::global()->openedWindowFor(item->identifier()), conn, sch, + kxi18n("You are about to delete query %1 but it is used by " + "following opened windows:") + .subs(sch->name())); + if (res != true) { + return res; + } return conn->dropQuery(sch); + } //last chance: just remove item return conn->removeObject(item->identifier()); } @@ -184,11 +196,70 @@ Q_UNUSED(newName); if (!KexiMainWindowIface::global()->project()->dbConnection()) return false; + // Note: unlike in KexiTablePart::rename here we just mark the query obsolete here so no need + // to call KexiQueryPart::askForClosingObjectsUsingQuerySchema(). KexiMainWindowIface::global()->project()->dbConnection() ->setQuerySchemaObsolete(item->name()); return true; } +// static +tristate KexiQueryPart::askForClosingObjectsUsingQuerySchema(KexiWindow *window, + KDbConnection *conn, + KDbQuerySchema *query, + const KLocalizedString &msg) +{ + Q_ASSERT(conn); + Q_ASSERT(query); + if (!window) { + return true; + } + QList listeners + = KDbTableSchemaChangeListener::listeners(conn, query); + KexiQueryPartTempData *temp = static_cast(window->data()); + // Special case: listener that is equal to window->data() will be silently closed + // without asking for confirmation. It is not counted when looking for objects that + // are "blocking" changes of the query. + const bool tempListenerExists = listeners.removeAll(temp) > 0; + // Immediate success if there's no temp-data's listener to close nor other listeners to close + if (!tempListenerExists && listeners.isEmpty()) { + return true; + } + + if (!listeners.isEmpty()) { + QString openedObjectsStr = "

    "; + for(const KDbTableSchemaChangeListener* listener : listeners) { + openedObjectsStr += QString("
  • %1
  • ").arg(listener->name()); + } + openedObjectsStr += "

"; + QString message = "" + + i18nc("@info/plain Sentence1 Sentence2 Sentence3", "%1%2%3", + KexiUtils::localizedStringToHtmlSubstring(msg), openedObjectsStr, + KexiUtils::localizedStringToHtmlSubstring( + kxi18nc("@info", "Do you want to close these windows and save the " + "design or cancel saving?"))) + + ""; + KGuiItem closeAndSaveItem(KStandardGuiItem::save()); + closeAndSaveItem.setText( + xi18nc("@action:button Close all windows and save", "Close Windows and Save")); + closeAndSaveItem.setToolTip(xi18nc("@info:tooltip Close all windows and save design", + "Close all windows and save design")); + const int r = KMessageBox::questionYesNo(window, message, QString(), closeAndSaveItem, + KStandardGuiItem::cancel(), QString(), + KMessageBox::Notify | KMessageBox::Dangerous); + if (r != KMessageBox::Yes) { + return cancelled; + } + } + // try to close every window depending on the query (if present) and also the temp-data's + // listener (if present) + const tristate res = KDbTableSchemaChangeListener::closeListeners(conn, query, { temp }); + if (res != true) { //do not expose closing errors twice; just cancel + return cancelled; + } + return true; +} + //---------------- KexiQueryPartTempData::KexiQueryPartTempData(KexiWindow* window, KDbConnection *conn) @@ -224,14 +295,13 @@ { if (!q) return; - foreach(const KDbTableSchema* table, *q->tables()) { - KDbTableSchemaChangeListener::registerForChanges(conn, this, table); - } + KDbTableSchemaChangeListener::registerForChanges(conn, this, q); } tristate KexiQueryPartTempData::closeListener() { KexiWindow* window = static_cast(parent()); + qDebug() << window->partItem()->name(); return KexiMainWindowIface::global()->closeWindow(window); } diff --git a/src/plugins/reports/KexiDBReportDataSource.h b/src/plugins/reports/KexiDBReportDataSource.h --- a/src/plugins/reports/KexiDBReportDataSource.h +++ b/src/plugins/reports/KexiDBReportDataSource.h @@ -1,6 +1,7 @@ /* * Kexi Report Plugin * Copyright (C) 2007-2017 by Adam Pigg +* Copyright (C) 2017 Jarosław Staniek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -22,9 +23,10 @@ #include #include -#include #include +class KexiReportPartTempData; + //! @brief Implementation of database report data source class KexiDBReportDataSource : public KReportDataSource { @@ -36,7 +38,8 @@ * -"org.kexi-project.query" * -empty QString() - attempt to resolve @a objectName */ - KexiDBReportDataSource(const QString &objectName, const QString& pluginId, KDbConnection *conn); + KexiDBReportDataSource(const QString &objectName, const QString &pluginId, + KexiReportPartTempData *data); virtual ~KexiDBReportDataSource(); virtual QStringList fieldNames() const; diff --git a/src/plugins/reports/KexiDBReportDataSource.cpp b/src/plugins/reports/KexiDBReportDataSource.cpp --- a/src/plugins/reports/KexiDBReportDataSource.cpp +++ b/src/plugins/reports/KexiDBReportDataSource.cpp @@ -1,6 +1,7 @@ /* * Kexi Report Plugin * Copyright (C) 2007-2017 by Adam Pigg +* Copyright (C) 2017 Jarosław Staniek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -17,7 +18,7 @@ */ #include "KexiDBReportDataSource.h" -#include +#include "kexireportpart.h" #include #include @@ -30,8 +31,8 @@ class Q_DECL_HIDDEN KexiDBReportDataSource::Private { public: - explicit Private(KDbConnection *pDb) - : cursor(0), connection(pDb), originalSchema(0), copySchema(0) + explicit Private(KexiReportPartTempData *data) + : cursor(0), tempData(data), originalSchema(0), copySchema(0) { } ~Private() @@ -44,15 +45,14 @@ QString objectName; KDbCursor *cursor; - KDbConnection *connection; + KexiReportPartTempData *tempData; KDbQuerySchema *originalSchema; KDbQuerySchema *copySchema; }; -KexiDBReportDataSource::KexiDBReportDataSource(const QString& objectName, - const QString& pluginId, - KDbConnection* pDb) - : d(new Private(pDb)) +KexiDBReportDataSource::KexiDBReportDataSource(const QString &objectName, const QString &pluginId, + KexiReportPartTempData *data) + : d(new Private(data)) { d->objectName = objectName; getSchema(pluginId); @@ -65,7 +65,7 @@ return; KDbOrderByColumnList order; for (int i = 0; i < sorting.count(); i++) { - if (!order.appendField(d->connection, d->copySchema, sorting[i].field(), + if (!order.appendField(d->tempData->connection(), d->copySchema, sorting[i].field(), KDbOrderByColumn::fromQt(sorting[i].order()))) { qWarning() << "Cannot set sort field" << i << sorting[i].field(); @@ -111,17 +111,17 @@ bool KexiDBReportDataSource::open() { - if ( d->connection && d->cursor == 0 ) + if ( d->tempData->connection() && d->cursor == 0 ) { if ( d->objectName.isEmpty() ) { return false; } else if ( d->copySchema) { qDebug() << "Opening cursor.." - << KDbConnectionAndQuerySchema(d->connection, *d->copySchema); - d->cursor = d->connection->executeQuery(d->copySchema, KDbCursor::Option::Buffered); + << KDbConnectionAndQuerySchema(d->tempData->connection(), *d->copySchema); + d->cursor = d->tempData->connection()->executeQuery(d->copySchema, KDbCursor::Option::Buffered); } @@ -140,54 +140,61 @@ { if (d->cursor) { const bool ok = d->cursor->close(); - d->connection->deleteCursor(d->cursor); + d->tempData->connection()->deleteCursor(d->cursor); d->cursor = nullptr; return ok; } return true; } bool KexiDBReportDataSource::getSchema(const QString& pluginId) { - if (d->connection) - { + if (d->tempData->connection()) { + KDbTableSchemaChangeListener::unregisterForChanges(d->tempData->connection(), d->tempData); delete d->originalSchema; d->originalSchema = 0; delete d->copySchema; d->copySchema = 0; + KDbTableSchema *table = nullptr; + KDbQuerySchema *query = nullptr; if ((pluginId.isEmpty() || pluginId == "org.kexi-project.table") - && d->connection->tableSchema(d->objectName)) + && (table = d->tempData->connection()->tableSchema(d->objectName))) { qDebug() << d->objectName << "is a table.."; - d->originalSchema = new KDbQuerySchema(d->connection->tableSchema(d->objectName)); + d->originalSchema = new KDbQuerySchema(table); } else if ((pluginId.isEmpty() || pluginId == "org.kexi-project.query") - && d->connection->querySchema(d->objectName)) + && (query = d->tempData->connection()->querySchema(d->objectName))) { qDebug() << d->objectName << "is a query.."; - qDebug() << KDbConnectionAndQuerySchema(d->connection, - *d->connection->querySchema(d->objectName)); - d->originalSchema - = new KDbQuerySchema(*(d->connection->querySchema(d->objectName)), d->connection); + qDebug() << KDbConnectionAndQuerySchema(d->tempData->connection(), *query); + d->originalSchema = new KDbQuerySchema(*query, d->tempData->connection()); } if (d->originalSchema) { - const KDbNativeStatementBuilder builder(d->connection, KDb::DriverEscaping); + const KDbNativeStatementBuilder builder(d->tempData->connection(), KDb::DriverEscaping); KDbEscapedString sql; if (builder.generateSelectStatement(&sql, d->originalSchema)) { qDebug() << "Original:" << sql; } else { qDebug() << "Original: ERROR"; + return false; } - qDebug() << KDbConnectionAndQuerySchema(d->connection, *d->originalSchema); + qDebug() << KDbConnectionAndQuerySchema(d->tempData->connection(), *d->originalSchema); - d->copySchema = new KDbQuerySchema(*d->originalSchema, d->connection); - qDebug() << KDbConnectionAndQuerySchema(d->connection, *d->copySchema); + d->copySchema = new KDbQuerySchema(*d->originalSchema, d->tempData->connection()); + qDebug() << KDbConnectionAndQuerySchema(d->tempData->connection(), *d->copySchema); if (builder.generateSelectStatement(&sql, d->copySchema)) { qDebug() << "Copy:" << sql; } else { qDebug() << "Copy: ERROR"; + return false; + } + if (table) { + KDbTableSchemaChangeListener::registerForChanges(d->tempData->connection(), d->tempData, table); + } else if (query) { + KDbTableSchemaChangeListener::registerForChanges(d->tempData->connection(), d->tempData, query); } } return true; @@ -206,7 +213,7 @@ return -1; } const KDbQueryColumnInfo::Vector fieldsExpanded(d->cursor->query()->fieldsExpanded( - d->connection, KDbQuerySchema::FieldsExpandedMode::Unique)); + d->tempData->connection(), KDbQuerySchema::FieldsExpandedMode::Unique)); for (int i = 0; i < fieldsExpanded.size(); ++i) { if (0 == QString::compare(fld, fieldsExpanded[i]->aliasOrName(), Qt::CaseInsensitive)) { return i; @@ -222,7 +229,7 @@ } QStringList names; const KDbQueryColumnInfo::Vector fieldsExpanded(d->originalSchema->fieldsExpanded( - d->connection, KDbQuerySchema::FieldsExpandedMode::Unique)); + d->tempData->connection(), KDbQuerySchema::FieldsExpandedMode::Unique)); for (int i = 0; i < fieldsExpanded.size(); i++) { //! @todo in some Kexi mode captionOrAliasOrName() would be used here (more user-friendly) names.append(fieldsExpanded[i]->aliasOrName()); @@ -289,7 +296,7 @@ qint64 KexiDBReportDataSource::recordCount() const { if (d->copySchema) { - return d->connection->recordCount(d->copySchema); + return d->tempData->connection()->recordCount(d->copySchema); } return 1; @@ -299,19 +306,19 @@ { //Get the list of queries in the database QStringList qs; - if (d->connection && d->connection->isConnected()) { - QList tids = d->connection->tableIds(); + if (d->tempData->connection() && d->tempData->connection()->isConnected()) { + QList tids = d->tempData->connection()->tableIds(); qs << ""; for (int i = 0; i < tids.size(); ++i) { - KDbTableSchema* tsc = d->connection->tableSchema(tids[i]); + KDbTableSchema* tsc = d->tempData->connection()->tableSchema(tids[i]); if (tsc) qs << tsc->name(); } - QList qids = d->connection->queryIds(); + QList qids = d->tempData->connection()->queryIds(); qs << ""; for (int i = 0; i < qids.size(); ++i) { - KDbQuerySchema* qsc = d->connection->querySchema(qids[i]); + KDbQuerySchema* qsc = d->tempData->connection()->querySchema(qids[i]); if (qsc) qs << qsc->name(); } @@ -322,5 +329,5 @@ KReportDataSource* KexiDBReportDataSource::create(const QString& source) const { - return new KexiDBReportDataSource(source, QString(), d->connection); + return new KexiDBReportDataSource(source, QString(), d->tempData); } diff --git a/src/plugins/reports/kexireportdesignview.h b/src/plugins/reports/kexireportdesignview.h --- a/src/plugins/reports/kexireportdesignview.h +++ b/src/plugins/reports/kexireportdesignview.h @@ -1,7 +1,7 @@ /* * Kexi Report Plugin * Copyright (C) 2007-2009 by Adam Pigg -* Copyright (C) 2011 Jarosław Staniek +* Copyright (C) 2011-2017 Jarosław Staniek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -49,8 +49,11 @@ void itemInserted(const QString& entity); private: - KReportDesigner *m_reportDesigner; KexiReportPartTempData* tempData() const; + QDomElement connectionData() const; + void setConnectionData(const QDomElement &c); + + KReportDesigner *m_reportDesigner; QScrollArea * m_scrollArea; //Actions diff --git a/src/plugins/reports/kexireportdesignview.cpp b/src/plugins/reports/kexireportdesignview.cpp --- a/src/plugins/reports/kexireportdesignview.cpp +++ b/src/plugins/reports/kexireportdesignview.cpp @@ -1,7 +1,7 @@ /* * Kexi Report Plugin * Copyright (C) 2007-2009 by Adam Pigg -* Copyright (C) 2011 Jarosław Staniek +* Copyright (C) 2011-2017 Jarosław Staniek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -22,6 +22,7 @@ #include #include "kexisourceselector.h" #include +#include #include @@ -113,7 +114,7 @@ QDomDocument doc("kexireport"); QDomElement root = doc.createElement("kexireport"); - QDomElement conndata = m_sourceSelector->connectionData(); + QDomElement conndata = connectionData(); if (conndata.isNull()) qDebug() << "Null conn data!"; @@ -162,7 +163,7 @@ } m_reportDesigner = new KReportDesigner(this, tempData()->reportDefinition); - m_sourceSelector->setConnectionData(tempData()->connectionDefinition); + setConnectionData(tempData()->connectionDefinition); m_reportDesigner->setScriptSource(qobject_cast(part())); } connect(m_reportDesigner, SIGNAL(itemInserted(QString)), this, SIGNAL(itemInserted(QString))); @@ -204,12 +205,42 @@ void KexiReportDesignView::slotDataSourceChanged() { - m_reportDesigner->setDataSource(m_sourceSelector->createDataSource()); - tempData()->connectionDefinition = m_sourceSelector->connectionData(); + if (m_sourceSelector->isSelectionValid()) { + m_reportDesigner->setDataSource(new KexiDBReportDataSource( + m_sourceSelector->selectedName(), m_sourceSelector->selectedPluginId(), tempData())); + tempData()->connectionDefinition = connectionData(); + } else { + m_reportDesigner->setDataSource(nullptr); + tempData()->connectionDefinition = QDomElement(); + } setDirty(true); } void KexiReportDesignView::triggerAction(const QString &action) { m_reportDesigner->slotItem(action); } + +QDomElement KexiReportDesignView::connectionData() const +{ + QDomDocument dd; + QDomElement conndata = dd.createElement("connection"); + conndata.setAttribute("type", "internal"); // for backward compatibility, currently always + // internal, we used to have "external" in old Kexi + conndata.setAttribute("source", m_sourceSelector->selectedName()); + conndata.setAttribute("class", m_sourceSelector->selectedPluginId()); + return conndata; +} + +void KexiReportDesignView::setConnectionData(const QDomElement &c) +{ + qDebug() << c; + if (c.attribute("type") == "internal") { + QString sourceClass(c.attribute("class")); + if (sourceClass != "org.kexi-project.table" && sourceClass != "org.kexi-project.query") { + sourceClass.clear(); // KexiDataSourceComboBox will try to find table, then query + } + m_sourceSelector->setDataSource(sourceClass, c.attribute("source")); + slotDataSourceChanged(); + } +} diff --git a/src/plugins/reports/kexireportpart.h b/src/plugins/reports/kexireportpart.h --- a/src/plugins/reports/kexireportpart.h +++ b/src/plugins/reports/kexireportpart.h @@ -1,7 +1,7 @@ /* * Kexi Report Plugin * Copyright (C) 2007-2008 by Adam Pigg - * Copyright (C) 2011-2015 Jarosław Staniek + * Copyright (C) 2011-2017 Jarosław Staniek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -25,19 +25,35 @@ #include #include + +#include + #include -class KexiReportPartTempData : public KexiWindowData +class KexiReportPartTempData : public KexiWindowData, public KDbTableSchemaChangeListener { Q_OBJECT public: - explicit KexiReportPartTempData(KexiWindow* parent); + KexiReportPartTempData(KexiWindow* parent, KDbConnection *conn); + ~KexiReportPartTempData(); QDomElement reportDefinition; QDomElement connectionDefinition; /*! true, if \a document member has changed in previous view. Used on view switching. Check this flag to see if we should refresh data for DataViewMode. */ bool reportSchemaChangedInPreviousView; + + KDbConnection *connection(); + +protected: + //! This temp-data acts as a listener for tracking changes in table schema + //! used by the report. This method closes the report on request. + tristate closeListener() override; + +private: + Q_DISABLE_COPY(KexiReportPartTempData) + class Private; + Private * const d; }; /** diff --git a/src/plugins/reports/kexireportpart.cpp b/src/plugins/reports/kexireportpart.cpp --- a/src/plugins/reports/kexireportpart.cpp +++ b/src/plugins/reports/kexireportpart.cpp @@ -1,7 +1,7 @@ /* * Kexi Report Plugin * Copyright (C) 2007-2008 by Adam Pigg - * Copyright (C) 2011-2015 Jarosław Staniek + * Copyright (C) 2011-2017 Jarosław Staniek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -34,6 +34,7 @@ #include #include "kexisourceselector.h" #include +#include //! @internal class Q_DECL_HIDDEN KexiReportPart::Private @@ -157,13 +158,47 @@ KexiWindowData* KexiReportPart::createWindowData(KexiWindow* window) { - return new KexiReportPartTempData(window); + KexiMainWindowIface *win = KexiMainWindowIface::global(); + return new KexiReportPartTempData(window, win->project()->dbConnection()); } -KexiReportPartTempData::KexiReportPartTempData(KexiWindow* parent) +//---------------- + +class Q_DECL_HIDDEN KexiReportPartTempData::Private +{ +public: + Private() + { + } + KDbConnection *conn; +}; + +KexiReportPartTempData::KexiReportPartTempData(KexiWindow* parent, KDbConnection *conn) : KexiWindowData(parent) , reportSchemaChangedInPreviousView(true /*to force reloading on startup*/) + , d(new Private) +{ + d->conn = conn; + setName(KexiUtils::localizedStringToHtmlSubstring( + kxi18nc("@info", "Report %1").subs(parent->partItem()->name()))); +} + +KexiReportPartTempData::~KexiReportPartTempData() +{ + KDbTableSchemaChangeListener::unregisterForChanges(d->conn, this); + delete d; +} + +KDbConnection* KexiReportPartTempData::connection() +{ + return d->conn; +} + +tristate KexiReportPartTempData::closeListener() { + KexiWindow* window = static_cast(parent()); + qDebug() << window->partItem()->name(); + return KexiMainWindowIface::global()->closeWindow(window); } void KexiReportPart::setupCustomPropertyPanelTabs(QTabWidget *tab) diff --git a/src/plugins/reports/kexireportview.cpp b/src/plugins/reports/kexireportview.cpp --- a/src/plugins/reports/kexireportview.cpp +++ b/src/plugins/reports/kexireportview.cpp @@ -22,10 +22,6 @@ #include "KexiDBReportDataSource.h" #ifndef KEXI_MOBILE #include - //! @todo KEXI3 -#if 0 -#include "keximigratereportdata.h" -#endif #endif #include #include @@ -405,22 +401,10 @@ KReportDataSource* KexiReportView::createDataSource(const QDomElement &e) { - KReportDataSource *kodata = 0; - if (e.attribute("type") == "internal" && !e.attribute("source").isEmpty()) { - kodata - = new KexiDBReportDataSource(e.attribute("source"), e.attribute("class"), - KexiMainWindowIface::global()->project()->dbConnection()); - } -#ifndef KEXI_MOBILE -//! @todo KEXI3 -#if 0 - if (e.attribute("type") == "external") { - kodata = new KexiMigrateReportData(e.attribute("source")); + return new KexiDBReportDataSource(e.attribute("source"), e.attribute("class"), tempData()); } -#endif -#endif - return kodata; + return nullptr; } KexiReportPartTempData* KexiReportView::tempData() const diff --git a/src/plugins/reports/kexisourceselector.h b/src/plugins/reports/kexisourceselector.h --- a/src/plugins/reports/kexisourceselector.h +++ b/src/plugins/reports/kexisourceselector.h @@ -42,9 +42,23 @@ ~KexiSourceSelector(); KReportDataSource* createDataSource() const Q_REQUIRED_RESULT; - void setConnectionData(const QDomElement &c); + QDomElement connectionData(); + //! @return name plugin ID of selected item (a table or a query). Can return an empty string. + QString selectedPluginId() const; + + //! @return name of selected table or query. + QString selectedName() const; + + //! \return true if the current selection is valid + bool isSelectionValid() const; + +public Q_SLOTS: + /*! Sets item for data source described by \a pluginId and \a name. + If \a pluginId is empty, either "org.kexi-project.table" and "org.kexi-project.query" are tried. */ + void setDataSource(const QString& pluginId, const QString& name); + Q_SIGNALS: void dataSourceChanged(); diff --git a/src/plugins/reports/kexisourceselector.cpp b/src/plugins/reports/kexisourceselector.cpp --- a/src/plugins/reports/kexisourceselector.cpp +++ b/src/plugins/reports/kexisourceselector.cpp @@ -20,7 +20,6 @@ #include "kexisourceselector.h" #include "KexiDataSourceComboBox.h" #include -#include #include @@ -70,36 +69,22 @@ delete d; } -void KexiSourceSelector::setConnectionData(const QDomElement &c) +QString KexiSourceSelector::selectedPluginId() const { - qDebug() << c; - if (c.attribute("type") == "internal") { - QString sourceClass(c.attribute("class")); - if (sourceClass != "org.kexi-project.table" && sourceClass != "org.kexi-project.query") { - sourceClass.clear(); // KexiDataSourceComboBox will try to find table, then query - } - d->dataSource->setDataSource(sourceClass, c.attribute("source")); - emit dataSourceChanged(); - } + return d->dataSource->selectedPluginId(); } -QDomElement KexiSourceSelector::connectionData() +QString KexiSourceSelector::selectedName() const { - QDomDocument dd; - QDomElement conndata = dd.createElement("connection"); - conndata.setAttribute("type", "internal"); // for backward compatibility, currently always - // internal, we used to have "external" in old Kexi - conndata.setAttribute("source", d->dataSource->selectedName()); - conndata.setAttribute("class", d->dataSource->selectedPluginId()); - return conndata; + return d->dataSource->selectedName(); } -KReportDataSource* KexiSourceSelector::createDataSource() const +bool KexiSourceSelector::isSelectionValid() const { - if (d->dataSource->isSelectionValid()) { - return new KexiDBReportDataSource(d->dataSource->selectedName(), - d->dataSource->selectedPluginId(), d->conn); - } - return nullptr; + return d->dataSource->isSelectionValid(); } +void KexiSourceSelector::setDataSource(const QString& pluginId, const QString& name) +{ + d->dataSource->setDataSource(pluginId, name); +} diff --git a/src/plugins/tables/kexitabledesignerview.h b/src/plugins/tables/kexitabledesignerview.h --- a/src/plugins/tables/kexitabledesignerview.h +++ b/src/plugins/tables/kexitabledesignerview.h @@ -1,5 +1,5 @@ /* This file is part of the KDE project - Copyright (C) 2004-2012 Jarosław Staniek + Copyright (C) 2004-2017 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 diff --git a/src/plugins/tables/kexitabledesignerview.cpp b/src/plugins/tables/kexitabledesignerview.cpp --- a/src/plugins/tables/kexitabledesignerview.cpp +++ b/src/plugins/tables/kexitabledesignerview.cpp @@ -1,5 +1,5 @@ /* This file is part of the KDE project - Copyright (C) 2004-2012 Jarosław Staniek + Copyright (C) 2004-2017 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 @@ -1424,11 +1424,11 @@ 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())); + window(), conn, tempData()->table(), + kxi18nc("@info", + "You are about to change the design of table %1 " + "but following objects using this table are open:") + .subs(tempData()->table()->name())); } if (res == true) { @@ -1462,8 +1462,13 @@ = static_cast(*tempData()->table()); res = buildSchema(*newTable); qDebug() << "BUILD SCHEMA:" << *newTable; - - res = conn->alterTable(tempData()->table(), newTable); + { + KDbTableSchema *oldTable = tempData()->table(); + tempData()->setTable(nullptr); // needed, otherwise setTable() will access dangling + // pointer after conn->alterTable() + KexiUtils::BoolBlocker guard(&tempData()->closeWindowOnCloseListener, false); + res = conn->alterTable(oldTable, newTable); + } if (res != true) window()->setStatus(conn, ""); } else { diff --git a/src/plugins/tables/kexitablepart.h b/src/plugins/tables/kexitablepart.h --- a/src/plugins/tables/kexitablepart.h +++ b/src/plugins/tables/kexitablepart.h @@ -58,14 +58,21 @@ We're checking this flag to see if we should refresh data for DataViewMode. */ bool tableSchemaChangedInPreviousView; + //! @c true indicates that closeListener() should close the table designer window. + //! This is disabled in one case: upon saving of the design of this table. + //! @see KexiTableDesignerView::storeData() + bool closeWindowOnCloseListener = true; + protected: //! Closes listener - this temp-data acts as a listener for tracking changes in table schema //! that is displayed in the window's data view. //! It just calls KexiDataTableView::setData(nullptr) is there's data set for the view //! (i.e. if KexiDataTableView::tableView()->data() is not @c nullptr). tristate closeListener() override; private: + void closeDataInDataView(); + Q_DISABLE_COPY(KexiTablePartTempData) class Private; Private * const d; @@ -84,21 +91,33 @@ virtual tristate rename(KexiPart::Item *item, const QString& newName); - //! Close objects that listenen to changes of the table schema @a table. - //! Asks the user for approval if there is at least one object that listens for changes - //! of the schema. If there is no approval, returns @c cancelled. - //! On failure returns @c false. - //! If @a window is @c nullptr, @c true is returned immediately because there is no window to - //! care about. - //! Special case: listener that is equal to window->data() will be silently closed - //! without asking for confirmation. It is not counted when looking for objects that - //! are "blocking" changes of @a table. - //! This exception is needed because the listener handles the data view's lifetime - //! and the data view should be reset silently without bothering the user. - //! See KexiTablePartTempData::closeListener() - static tristate askForClosingObjectsUsingTableSchema( - KexiWindow *window, KDbConnection *conn, - KDbTableSchema *table, const QString& msg); + /** + * Closes objects that listenen to changes of the table schema @a table, i.e. use it. + * + * These objects can be currently: + * - lookup fields of other tables + * - queries using the table directly or via lookup fields + * - forms and reports that use the table directly as data source or via query. + * + * Scripts referencing the table programatically are not analyzed, so they can fail on next + * execution. + * + * This method asks the user for approval if there is at least one object that listens for + * changes of the schema (altering, renaming or removal). If there is no approval, returns + * @c cancelled. On failure @c false is returned. If @a window is @c nullptr, @c true is + * returned immediately because there is no window to care about. + * + * Special case: listener for the table @a table will be silently closed without asking for + * confirmation. It is ignored when looking for objects that are "blocking" changes + * of @a table. This exception is needed because the listener handles the data view's lifetime + * and the data view should be reset silently without bothering the user. + * + * @see KexiTablePartTempData::closeListener() + * @see KexiQueryPart::askForClosingObjectsUsingQuerySchema() + */ + static tristate askForClosingObjectsUsingTableSchema(KexiWindow *window, KDbConnection *conn, + KDbTableSchema *table, + const KLocalizedString &msg); virtual KLocalizedString i18nMessage(const QString& englishMessage, KexiWindow* window) const; diff --git a/src/plugins/tables/kexitablepart.cpp b/src/plugins/tables/kexitablepart.cpp --- a/src/plugins/tables/kexitablepart.cpp +++ b/src/plugins/tables/kexitablepart.cpp @@ -32,6 +32,7 @@ #include "kexitabledesigner_dataview.h" #include "kexilookupcolumnpage.h" #include +#include #include @@ -133,8 +134,9 @@ if (sch) { const tristate res = KexiTablePart::askForClosingObjectsUsingTableSchema( KexiMainWindowIface::global()->openedWindowFor(item->identifier()), conn, sch, - xi18n("You are about to remove table %1 but following objects using this table are opened:", - sch->name())); + kxi18n("You are about to delete table %1 but it is used by " + "following opened windows:") + .subs(sch->name())); if (res != true) { return res; } @@ -153,8 +155,9 @@ return false; const tristate res = KexiTablePart::askForClosingObjectsUsingTableSchema( KexiMainWindowIface::global()->openedWindowFor(item->identifier()), conn, schema, - xi18n("You are about to rename table %1 but following objects using this table are opened:", - schema->name())); + kxi18n("You are about to rename table %1 but it is used by " + "following opened windows:") + .subs(schema->name())); if (res != true) { return res; } @@ -171,10 +174,11 @@ return KexiMainWindowIface::global()->project()->dbConnection()->tableSchema(object.name()); } -//static -tristate KexiTablePart::askForClosingObjectsUsingTableSchema( - KexiWindow *window, KDbConnection *conn, - KDbTableSchema *table, const QString& msg) +// static +tristate KexiTablePart::askForClosingObjectsUsingTableSchema(KexiWindow *window, + KDbConnection *conn, + KDbTableSchema *table, + const KLocalizedString &msg) { Q_ASSERT(conn); Q_ASSERT(table); @@ -194,23 +198,33 @@ } if (!listeners.isEmpty()) { - QString openedObjectsStr = ""; + QString openedObjectsStr = "

    "; for(const KDbTableSchemaChangeListener* listener : listeners) { - openedObjectsStr += QString("%1").arg(listener->name()); + openedObjectsStr += QString("
  • %1
  • ").arg(listener->name()); } - openedObjectsStr += ""; - const int r = KMessageBox::questionYesNo(window, - i18nc("@info", "%1%2", msg, openedObjectsStr) - + "" - + xi18n("Do you want to close all windows for these objects?") - + "", - QString(), KGuiItem(xi18nc("@action:button Close All Windows", "Close Windows"), koIconName("window-close")), KStandardGuiItem::cancel()); + openedObjectsStr += "

"; + QString message = "" + + i18nc("@info/plain Sentence1 Sentence2 Sentence3", "%1%2%3", + KexiUtils::localizedStringToHtmlSubstring(msg), openedObjectsStr, + KexiUtils::localizedStringToHtmlSubstring( + kxi18nc("@info", "Do you want to close these windows and save the " + "design or cancel saving?"))) + + ""; + KGuiItem closeAndSaveItem(KStandardGuiItem::save()); + closeAndSaveItem.setText( + xi18nc("@action:button Close all windows and save", "Close Windows and Save")); + closeAndSaveItem.setToolTip(xi18nc("@info:tooltip Close all windows and save design", + "Close all windows and save design")); + const int r = KMessageBox::questionYesNo(window, message, QString(), closeAndSaveItem, + KStandardGuiItem::cancel(), QString(), + KMessageBox::Notify | KMessageBox::Dangerous); if (r != KMessageBox::Yes) { return cancelled; } } - //try to close every window depending on the table (if present) and also the temp-data's listener (if present) - const tristate res = KDbTableSchemaChangeListener::closeListeners(conn, table); + // try to close every window depending on the table (if present) and also the temp-data's + // listener (if present) + const tristate res = KDbTableSchemaChangeListener::closeListeners(conn, table, { temp }); if (res != true) { //do not expose closing errors twice; just cancel return cancelled; } @@ -306,14 +320,27 @@ return d->conn; } +void KexiTablePartTempData::closeDataInDataView() +{ + const KexiWindow* window = static_cast(parent()); + if (window->currentViewMode() != Kexi::DataViewMode) { + KexiTableDesigner_DataView *dataView + = qobject_cast(window->viewForMode(Kexi::DataViewMode)); + if (dataView && dataView->tableView()->data()) { + dataView->setData(nullptr); + } + } +} + void KexiTablePartTempData::setTable(KDbTableSchema *table) { if (d->table == table) { return; } if (d->table) { - KDbTableSchemaChangeListener::unregisterForChanges(d->conn, this, d->table); + KDbTableSchemaChangeListener::unregisterForChanges(d->conn, d->table); } + closeDataInDataView(); d->table = table; if (d->table) { KDbTableSchemaChangeListener::registerForChanges(d->conn, this, d->table); @@ -323,12 +350,10 @@ tristate KexiTablePartTempData::closeListener() { KexiWindow* window = static_cast(parent()); - if (window->currentViewMode() != Kexi::DataViewMode) { - KexiTableDesigner_DataView *dataView - = qobject_cast(window->viewForMode(Kexi::DataViewMode)); - if (dataView && dataView->tableView()->data()) { - dataView->setData(nullptr); - } + qDebug() << window->partItem()->name(); + closeDataInDataView(); + if (closeWindowOnCloseListener) { + return KexiMainWindowIface::global()->closeWindow(window); } return true; } diff --git a/src/widget/dataviewcommon/kexiformdataiteminterface.h b/src/widget/dataviewcommon/kexiformdataiteminterface.h --- a/src/widget/dataviewcommon/kexiformdataiteminterface.h +++ b/src/widget/dataviewcommon/kexiformdataiteminterface.h @@ -160,7 +160,7 @@ protected: QString m_dataSource; - QString m_dataSourcePartClass; + QString m_dataSourcePartClass = QStringLiteral("org.kexi-project.table"); //!< default for cases when the part class is missing KDbQueryColumnInfo* m_columnInfo; KexiDisplayUtils::DisplayParameters *m_displayParametersForEnteredValue; //!< used in setDisplayDefaultValue() KexiDisplayUtils::DisplayParameters *m_displayParametersForDefaultValue; //!< used in setDisplayDefaultValue()