diff --git a/src/plugins/reports/KexiDBReportDataSource.cpp b/src/plugins/reports/KexiDBReportDataSource.cpp index dfe92268a..fdeddbc47 100644 --- a/src/plugins/reports/KexiDBReportDataSource.cpp +++ b/src/plugins/reports/KexiDBReportDataSource.cpp @@ -1,333 +1,383 @@ /* * Kexi Report Plugin * Copyright (C) 2007-2017 by Adam Pigg -* Copyright (C) 2017 Jarosław Staniek +* Copyright (C) 2017-2018 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 * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "KexiDBReportDataSource.h" #include "kexireportpart.h" #include #include #include #include #include #include class Q_DECL_HIDDEN KexiDBReportDataSource::Private { public: explicit Private(KexiReportPartTempData *data) : cursor(0), tempData(data), originalSchema(0), copySchema(0) { } ~Private() { delete copySchema; delete originalSchema; } QString objectName; KDbCursor *cursor; KexiReportPartTempData *tempData; KDbQuerySchema *originalSchema; KDbQuerySchema *copySchema; + KDbEscapedString schemaSql; }; KexiDBReportDataSource::KexiDBReportDataSource(const QString &objectName, const QString &pluginId, KexiReportPartTempData *data) : d(new Private(data)) { d->objectName = objectName; getSchema(pluginId); } void KexiDBReportDataSource::setSorting(const QList& sorting) { if (d->copySchema) { if (sorting.isEmpty()) return; KDbOrderByColumnList order; for (int i = 0; i < sorting.count(); i++) { 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(); return; } } d->copySchema->setOrderByColumnList(order); } else { qWarning() << "Unable to sort null schema"; } } void KexiDBReportDataSource::addCondition(const QString &field, const QVariant &value, const QString& relation) { if (d->copySchema) { KDbField *fld = d->copySchema->findTableField(field); if (fld) { if (relation.length() == 1) { QString errorMessage; QString errorDescription; if (!d->copySchema->addToWhereExpression(fld, value, KDbToken(relation.toLatin1()[0]), &errorMessage, &errorDescription)) { qWarning() << "Invalid expression cannot be added to WHERE:" << fld << relation << value; qWarning() << "addToWhereExpression() failed, message=" << errorMessage << "description=" << errorDescription; } } else { qWarning() << "Invalid relation passed in:" << relation; } } } else { qDebug() << "Unable to add expresstion to null schema"; } } KexiDBReportDataSource::~KexiDBReportDataSource() { close(); delete d; } bool KexiDBReportDataSource::open() { if ( d->tempData->connection() && d->cursor == 0 ) { if ( d->objectName.isEmpty() ) { return false; } else if ( d->copySchema) { qDebug() << "Opening cursor.." << KDbConnectionAndQuerySchema(d->tempData->connection(), *d->copySchema); d->cursor = d->tempData->connection()->executeQuery(d->copySchema, KDbCursor::Option::Buffered); } if ( d->cursor ) { qDebug() << "Moving to first record.."; return d->cursor->moveFirst(); } else return false; } return false; } bool KexiDBReportDataSource::close() { if (d->cursor) { const bool ok = d->cursor->close(); d->tempData->connection()->deleteCursor(d->cursor); d->cursor = nullptr; return ok; } return true; } bool KexiDBReportDataSource::getSchema(const QString& pluginId) { 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") && (table = d->tempData->connection()->tableSchema(d->objectName))) { qDebug() << d->objectName << "is a table.."; d->originalSchema = new KDbQuerySchema(table); } else if ((pluginId.isEmpty() || pluginId == "org.kexi-project.query") && (query = d->tempData->connection()->querySchema(d->objectName))) { qDebug() << d->objectName << "is a query.."; qDebug() << KDbConnectionAndQuerySchema(d->tempData->connection(), *query); d->originalSchema = new KDbQuerySchema(*query, d->tempData->connection()); } if (d->originalSchema) { 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->tempData->connection(), *d->originalSchema); 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; + if (builder.generateSelectStatement(&d->schemaSql, d->copySchema)) { + qDebug() << "Copy:" << d->schemaSql; } 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; } return false; } QString KexiDBReportDataSource::sourceName() const { return d->objectName; } int KexiDBReportDataSource::fieldNumber ( const QString &fld ) const { if (!d->cursor || !d->cursor->query()) { return -1; } const KDbQueryColumnInfo::Vector fieldsExpanded(d->cursor->query()->fieldsExpanded( 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; } } return -1; } QStringList KexiDBReportDataSource::fieldNames() const { if (!d->originalSchema) { return QStringList(); } QStringList names; const KDbQueryColumnInfo::Vector fieldsExpanded(d->originalSchema->fieldsExpanded( 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()); } return names; } QVariant KexiDBReportDataSource::value (int i) const { if ( d->cursor ) return d->cursor->value ( i ); return QVariant(); } QVariant KexiDBReportDataSource::value ( const QString &fld ) const { int i = fieldNumber ( fld ); if (d->cursor && i >= 0) return d->cursor->value ( i ); return QVariant(); } bool KexiDBReportDataSource::moveNext() { if ( d->cursor ) return d->cursor->moveNext(); return false; } bool KexiDBReportDataSource::movePrevious() { if ( d->cursor ) return d->cursor->movePrev(); return false; } bool KexiDBReportDataSource::moveFirst() { if ( d->cursor ) return d->cursor->moveFirst(); return false; } bool KexiDBReportDataSource::moveLast() { if ( d->cursor ) return d->cursor->moveLast(); return false; } qint64 KexiDBReportDataSource::at() const { if ( d->cursor ) return d->cursor->at(); return 0; } qint64 KexiDBReportDataSource::recordCount() const { if (d->copySchema) { return d->tempData->connection()->recordCount(d->copySchema); } return 1; } +double KexiDBReportDataSource::runAggregateFunction(const QString &function, const QString &field, + const QMap &conditions) +{ + double numberResult = 0.0; + if (d->schemaSql.isEmpty()) { + qWarning() << "No query for running aggregate function" << function << field; + return numberResult; + } + KDbEscapedString whereSql; + KDbConnection *conn = d->tempData->connection(); + if (!conditions.isEmpty()) { + for (QMap::ConstIterator it = conditions.constBegin(); + it != conditions.constEnd(); ++it) + { + if (!whereSql.isEmpty()) { + whereSql.append(" AND "); + } + KDbQueryColumnInfo *cinfo = d->copySchema->columnInfo(conn, it.key()); + if (!cinfo) { + qWarning() << "Could not find column" << it.key() << "for condition" << it.key() + << "=" << it.value(); + return numberResult; + } + whereSql.append( + KDbEscapedString(d->tempData->connection()->escapeIdentifier(cinfo->aliasOrName())) + + " = " + + d->tempData->connection()->driver()->valueToSql(cinfo->field(), it.value())); + } + whereSql.prepend(" WHERE "); + } + + const KDbEscapedString sql = KDbEscapedString("SELECT " + function + "(" + field + ") FROM (" + + d->schemaSql + ")" + whereSql); + QString stringResult; + const tristate res = d->tempData->connection()->querySingleString(sql, &stringResult); + if (res != true) { + qWarning() << "Failed to execute query for running aggregate function" << function << field; + return numberResult; + } + bool ok; + numberResult = stringResult.toDouble(&ok); + if (!ok) { + qWarning() << "Result of query for running aggregate function" << function << field + << "is not a number (" << stringResult << ")"; + return numberResult; + } + return numberResult; +} + QStringList KexiDBReportDataSource::dataSourceNames() const { //Get the list of queries in the database QStringList qs; 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->tempData->connection()->tableSchema(tids[i]); if (tsc) qs << tsc->name(); } QList qids = d->tempData->connection()->queryIds(); qs << ""; for (int i = 0; i < qids.size(); ++i) { KDbQuerySchema* qsc = d->tempData->connection()->querySchema(qids[i]); if (qsc) qs << qsc->name(); } } return qs; } KReportDataSource* KexiDBReportDataSource::create(const QString& source) const { return new KexiDBReportDataSource(source, QString(), d->tempData); } diff --git a/src/plugins/reports/KexiDBReportDataSource.h b/src/plugins/reports/KexiDBReportDataSource.h index 18342e25b..c8e8885dc 100644 --- a/src/plugins/reports/KexiDBReportDataSource.h +++ b/src/plugins/reports/KexiDBReportDataSource.h @@ -1,81 +1,96 @@ /* * Kexi Report Plugin * Copyright (C) 2007-2017 by Adam Pigg -* Copyright (C) 2017 Jarosław Staniek +* Copyright (C) 2017-2018 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 * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #ifndef __KEXIDBREPORTDATA_H__ #define __KEXIDBREPORTDATA_H__ #include #include #include class KexiReportPartTempData; //! @brief Implementation of database report data source class KexiDBReportDataSource : public KReportDataSource { public: /*! * @a pluginId specifies type of @a objectName, a table or query. * Types accepted: * -"org.kexi-project.table" * -"org.kexi-project.query" * -empty QString() - attempt to resolve @a objectName */ KexiDBReportDataSource(const QString &objectName, const QString &pluginId, KexiReportPartTempData *data); virtual ~KexiDBReportDataSource(); virtual QStringList fieldNames() const; virtual void setSorting(const QList& sorting); //! Adds a condition to the data source. //! @note Only single-character relation operators such as "=" or ">" are supported now. //! @todo Use KDb parser to support all relation operators such as ">=". virtual void addCondition(const QString &field, const QVariant &value, const QString &relation = QLatin1String("=")); virtual QString sourceName() const; virtual int fieldNumber(const QString &field) const; virtual QVariant value(int) const; virtual QVariant value(const QString &field) const; virtual bool open(); virtual bool close(); virtual bool moveNext(); virtual bool movePrevious(); virtual bool moveFirst(); virtual bool moveLast(); virtual qint64 at() const; virtual qint64 recordCount() const; + /** + * Runs aggregate function @a function on the data source + * + * @param function name such as max, min, avg + * @param field name of field for which the aggregation should be executed + * @param conditions optional conditions that limit the record set + * @return value of the function, 0.0 on failure + * + * @warning SQL injection warning: validity of @a function name is not checked, this should not + * be part of a public API. + * @todo Move SQL aggregate functions to KDb. Current code depends on support for subqueries. + */ + double runAggregateFunction(const QString &function, const QString &field, + const QMap &conditions); + //Utility Functions virtual QStringList dataSourceNames() const; virtual KReportDataSource* create(const QString& source) const Q_REQUIRED_RESULT; private: class Private; Private * const d; bool getSchema(const QString& pluginId); }; #endif diff --git a/src/plugins/reports/kexireportview.cpp b/src/plugins/reports/kexireportview.cpp index 61dcaff1e..aaee5c4f6 100644 --- a/src/plugins/reports/kexireportview.cpp +++ b/src/plugins/reports/kexireportview.cpp @@ -1,476 +1,476 @@ /* * Kexi Report Plugin * Copyright (C) 2007-2008 by Adam Pigg (adam@piggz.co.uk) - * Copyright (C) 2014-2017 Jarosław Staniek + * Copyright (C) 2014-2018 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 * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "kexireportview.h" #include #include "KexiDBReportDataSource.h" #ifndef KEXI_MOBILE #include #endif #include #include #include //! @todo KEXI3 #include "../scripting/kexiscripting/kexiscriptadaptor.h" #include #include #include #include #include "krscriptfunctions.h" #include #include #include #include #include #include #include #include #include #include #include #include KexiReportView::KexiReportView(QWidget *parent) : KexiView(parent), m_preRenderer(0), m_functions(0) //! @todo KEXI3, m_kexi(0) { setObjectName("KexiReportDesigner_DataView"); m_reportView = new KReportView(this); layout()->addWidget(m_reportView); #ifndef KEXI_MOBILE m_pageSelector = new KexiRecordNavigator(*m_reportView->scrollArea(), m_reportView); m_pageSelector->setInsertingButtonVisible(false); m_pageSelector->setInsertingEnabled(false); m_pageSelector->setLabelText(xi18nc("Page selector label", "Page:")); m_pageSelector->setButtonToolTipText(KexiRecordNavigator::ButtonFirst, xi18n("Go to first page")); m_pageSelector->setButtonWhatsThisText(KexiRecordNavigator::ButtonFirst, xi18n("Goes to first page")); m_pageSelector->setButtonToolTipText(KexiRecordNavigator::ButtonPrevious, xi18n("Go to previous page")); m_pageSelector->setButtonWhatsThisText(KexiRecordNavigator::ButtonPrevious, xi18n("Goes to previous page")); m_pageSelector->setButtonToolTipText(KexiRecordNavigator::ButtonNext, xi18n("Go to next page")); m_pageSelector->setButtonWhatsThisText(KexiRecordNavigator::ButtonNext, xi18n("Goes to next page")); m_pageSelector->setButtonToolTipText(KexiRecordNavigator::ButtonLast, xi18n("Go to last page")); m_pageSelector->setButtonWhatsThisText(KexiRecordNavigator::ButtonLast, xi18n("Goes to last page")); m_pageSelector->setNumberFieldToolTips(xi18n("Current page number"), xi18n("Number of pages")); m_pageSelector->setRecordHandler(this); #endif // -- setup local actions QList viewActions; QAction* a; #ifndef KEXI_MOBILE viewActions << (a = new QAction(koIcon("document-print"), xi18n("Print"), this)); a->setObjectName("print_report"); a->setToolTip(xi18n("Print report")); a->setWhatsThis(xi18n("Prints the current report.")); connect(a, SIGNAL(triggered()), this, SLOT(slotPrintReport())); KActionMenu *exportMenu = new KActionMenu(koIcon("document-export"), xi18nc("@title:menu","E&xport As"), this); exportMenu->setObjectName("report_export_as"); exportMenu->setDelayed(false); #endif #ifdef KEXI_SHOW_UNFINISHED #ifdef KEXI_MOBILE viewActions << (a = new QAction(xi18n("Export:"), this)); a->setEnabled(false); //!TODO this is a bit of a dirty way to add what looks like a label to the toolbar! // " ", not "", is said to be needed in maemo, the icon didn't display properly without it viewActions << (a = new QAction(koIcon("application-vnd.oasis.opendocument.text"), QLatin1String(" "), this)); #else exportMenu->addAction(a = new QAction(koIcon("application-vnd.oasis.opendocument.text"), xi18nc("open dialog to export as text document", "Text Document..."), this)); #endif a->setObjectName("export_as_text_document"); a->setToolTip(xi18n("Export the report as a text document (in OpenDocument Text format)")); a->setWhatsThis(xi18n("Exports the report as a text document (in OpenDocument Text format).")); a->setEnabled(true); connect(a, SIGNAL(triggered()), this, SLOT(slotExportAsTextDocument())); #endif #ifdef KEXI_MOBILE viewActions << (a = new QAction(koIcon("application-pdf"), QLatin1String(" "), this)); #else exportMenu->addAction(a = new QAction(koIcon("application-pdf"), xi18nc("Portable Document Format...", "PDF..."), this)); #endif a->setObjectName("export_as_pdf"); a->setToolTip(xi18n("Export as PDF")); a->setWhatsThis(xi18n("Exports the current report as PDF.")); a->setEnabled(true); connect(a, SIGNAL(triggered()), this, SLOT(slotExportAsPdf())); #ifdef KEXI_SHOW_UNFINISHED #ifdef KEXI_MOBILE viewActions << (a = new QAction(koIcon("application-vnd.oasis.opendocument.spreadsheet"), QLatin1String(" "), this)); #else exportMenu->addAction(a = new QAction(koIcon("application-vnd.oasis.opendocument.spreadsheet"), xi18nc("open dialog to export as spreadsheet", "Spreadsheet..."), this)); #endif a->setObjectName("export_as_spreadsheet"); a->setToolTip(xi18n("Export the report as a spreadsheet (in OpenDocument Spreadsheet format)")); a->setWhatsThis(xi18n("Exports the report as a spreadsheet (in OpenDocument Spreadsheet format).")); a->setEnabled(true); connect(a, SIGNAL(triggered()), this, SLOT(slotExportAsSpreadsheet())); #endif #ifdef KEXI_MOBILE viewActions << (a = new QAction(koIcon("text-html"), QLatin1String(" "), this)); #else exportMenu->addAction(a = new QAction(koIcon("text-html"), xi18nc("open dialog to export as web page", "Web Page..."), this)); #endif a->setObjectName("export_as_web_page"); a->setToolTip(xi18n("Export the report as a web page (in HTML format)")); a->setWhatsThis(xi18n("Exports the report as a web page (in HTML format).")); a->setEnabled(true); connect(a, SIGNAL(triggered()), this, SLOT(slotExportAsWebPage())); setViewActions(viewActions); #ifndef KEXI_MOBILE // setup main menu actions QList mainMenuActions; mainMenuActions << exportMenu; setMainMenuActions(mainMenuActions); #endif } KexiReportView::~KexiReportView() { delete m_preRenderer; } void KexiReportView::slotPrintReport() { QScopedPointer renderer(m_factory.createInstance("print")); if (!renderer) { return; } QPrinter printer(QPrinter::HighResolution); QPrintDialog dialog(&printer, this); if (dialog.exec() == QDialog::Accepted) { KReportRendererContext cxt; QPainter painter; cxt.setPrinter(&printer); cxt.setPainter(&painter); if (!renderer->render(cxt, m_preRenderer->document())) { KMessageBox::error(this, xi18n("Printing the report failed."), xi18n("Print Failed")); } } } void KexiReportView::slotExportAsPdf() { QScopedPointer renderer(m_factory.createInstance("print")); if (renderer) { KReportRendererContext cxt; cxt.setUrl(getExportUrl(QLatin1String("application/pdf"), xi18n("Export Report as PDF"), "kfiledialog:///LastVisitedPDFExportPath/", "pdf")); if (!cxt.url().isValid()) { return; } QPrinter printer; QPainter painter; printer.setOutputFileName(cxt.url().path()); printer.setOutputFormat(QPrinter::PdfFormat); printer.setColorMode(QPrinter::Color); painter.begin(&printer); cxt.setPrinter(&printer); cxt.setPainter(&painter); if (!renderer->render(cxt, m_preRenderer->document())) { KMessageBox::error(this, xi18n("Exporting the report as PDF to %1 failed.", cxt.url().toDisplayString()), xi18n("Export Failed")); } else { openExportedDocument(cxt.url()); } } } QUrl KexiReportView::getExportUrl(const QString &mimetype, const QString &caption, const QString &lastExportPath, const QString &extension) { QString defaultSavePath; QString recentDirClass; //TODO use utils defaultSavePath = KFileWidget::getStartUrl(QUrl(lastExportPath), recentDirClass).toLocalFile() + '/' + window()->partItem()->captionOrName() + '.' + extension; // loop until an url has been chosen or the file selection has been cancelled const QMimeDatabase db; const QString filterString = db.mimeTypeForName(mimetype).filterString(); return QFileDialog::getSaveFileUrl(this, caption, QUrl(defaultSavePath), filterString); } void KexiReportView::openExportedDocument(const QUrl &destination) { const int answer = KMessageBox::questionYesNo( this, xi18n("Do you want to open exported document?"), QString(), KStandardGuiItem::open(), KStandardGuiItem::close()); if (answer == KMessageBox::Yes) { (void)new KRun(destination, this->topLevelWidget()); } } #ifdef KEXI_SHOW_UNFINISHED void KexiReportView::slotExportAsSpreadsheet() { QScopedPointer renderer(m_factory.createInstance("ods")); if (renderer) { KReportRendererContext cxt; cxt.setUrl(getExportUrl(QLatin1String("application/vnd.oasis.opendocument.spreadsheet"), xi18n("Export Report as Spreadsheet"), "kfiledialog:///LastVisitedODSExportPath/", "ods")); if (!cxt.url().isValid()) { return; } if (!renderer->render(cxt, m_preRenderer->document())) { KMessageBox::error(this, xi18n("Failed to export the report as spreadsheet to %1.", cxt.url().toDisplayString()), xi18n("Export Failed")); } else { openExportedDocument(cxt.url()); } } } void KexiReportView::slotExportAsTextDocument() { QScopedPointer renderer(m_factory.createInstance("odt")); //! @todo Show error or don't show the commands to the user if the plugin isn't available. //! The same for other createInstance() calls. if (renderer) { KReportRendererContext cxt; cxt.setUrl(getExportUrl(QLatin1String("application/vnd.oasis.opendocument.text"), xi18n("Export Report as Text Document"), "kfiledialog:///LastVisitedODTExportPath/", "odt")); if (!cxt.url().isValid()) { return; } if (!renderer->render(cxt, m_preRenderer->document())) { KMessageBox::error(this, xi18n("Exporting the report as text document to %1 failed.", cxt.url().toDisplayString()), xi18n("Export Failed")); } else { openExportedDocument(cxt.url()); } } } #endif void KexiReportView::slotExportAsWebPage() { const QString dialogTitle = xi18n("Export Report as Web Page"); KReportRendererContext cxt; cxt.setUrl(getExportUrl(QLatin1String("text/html"), dialogTitle, "kfiledialog:///LastVisitedHTMLExportPath/", "html")); if (!cxt.url().isValid()) { return; } const int answer = KMessageBox::questionYesNo( this, xi18nc("@info", "Would you like to use Cascading Style Sheets (CSS) in the exported " "web page or use HTML tables?" "CSS give output closer to the original."), dialogTitle, KGuiItem(xi18nc("@action:button", "Use CSS")), KGuiItem(xi18nc("@action:button", "Use Table"))); QScopedPointer renderer( m_factory.createInstance(answer == KMessageBox::Yes ? "htmlcss" : "htmltable")); if (!renderer) { return; } if (!renderer->render(cxt, m_preRenderer->document())) { KMessageBox::error(this, xi18n("Exporting the report as web page to %1 failed.", cxt.url().toDisplayString()), xi18n("Export Failed")); } else { openExportedDocument(cxt.url()); } } tristate KexiReportView::beforeSwitchTo(Kexi::ViewMode mode, bool *dontStore) { Q_UNUSED(mode); Q_UNUSED(dontStore); return true; } tristate KexiReportView::afterSwitchFrom(Kexi::ViewMode mode) { Q_UNUSED(mode); if (tempData()->reportSchemaChangedInPreviousView) { tempData()->reportSchemaChangedInPreviousView = false; delete m_preRenderer; //qDebug() << tempData()->reportDefinition.tagName(); m_preRenderer = new KReportPreRenderer(tempData()->reportDefinition); if (m_preRenderer->isValid()) { - KReportDataSource *reportData = 0; + KexiDBReportDataSource *reportData = nullptr; if (!tempData()->connectionDefinition.isNull()) { reportData = createDataSource(tempData()->connectionDefinition); } m_preRenderer->setDataSource(reportData); m_preRenderer->setScriptSource(qobject_cast(part())); m_preRenderer->setName(window()->partItem()->name()); //Add a kexi object to provide kexidb and extra functionality //! @todo KEXI3 if we want this if(!m_kexi) { // m_kexi = new KexiScriptAdaptor(); // } // m_preRenderer->registerScriptObject(m_kexi, "Kexi"); //If using a kexidb source, add a functions scripting object - if (tempData()->connectionDefinition.attribute("type") == "internal") { - m_functions = new KRScriptFunctions(reportData, KexiMainWindowIface::global()->project()->dbConnection()); + if (reportData && tempData()->connectionDefinition.attribute("type") == "internal") { + m_functions = new KRScriptFunctions(reportData); m_preRenderer->registerScriptObject(m_functions, "field"); connect(m_preRenderer, SIGNAL(groupChanged(QMap)), m_functions, SLOT(setGroupData(QMap))); } connect(m_preRenderer, SIGNAL(finishedAllASyncItems()), this, SLOT(finishedAllASyncItems())); if (!m_preRenderer->generateDocument()) { qWarning() << "Could not generate report document"; return false; } m_reportView->setDocument(m_preRenderer->document()); #ifndef KEXI_MOBILE m_pageSelector->setRecordCount(m_reportView->pageCount()); m_pageSelector->setCurrentRecordNumber(1); #endif } else { KMessageBox::error(this, xi18n("Report schema appears to be invalid or corrupt"), xi18n("Opening failed")); } } return true; } -KReportDataSource* KexiReportView::createDataSource(const QDomElement &e) +KexiDBReportDataSource* KexiReportView::createDataSource(const QDomElement &e) { if (e.attribute("type") == "internal" && !e.attribute("source").isEmpty()) { return new KexiDBReportDataSource(e.attribute("source"), e.attribute("class"), tempData()); } return nullptr; } KexiReportPartTempData* KexiReportView::tempData() const { return static_cast(window()->data()); } void KexiReportView::addNewRecordRequested() { } void KexiReportView::moveToFirstRecordRequested() { m_reportView->moveToFirstPage(); #ifndef KEXI_MOBILE m_pageSelector->setCurrentRecordNumber(m_reportView->currentPage()); #endif } void KexiReportView::moveToLastRecordRequested() { m_reportView->moveToLastPage(); #ifndef KEXI_MOBILE m_pageSelector->setCurrentRecordNumber(m_reportView->currentPage()); #endif } void KexiReportView::moveToNextRecordRequested() { m_reportView->moveToNextPage(); #ifndef KEXI_MOBILE m_pageSelector->setCurrentRecordNumber(m_reportView->currentPage()); #endif } void KexiReportView::moveToPreviousRecordRequested() { m_reportView->moveToPreviousPage(); #ifndef KEXI_MOBILE m_pageSelector->setCurrentRecordNumber(m_reportView->currentPage()); #endif } void KexiReportView::moveToRecordRequested(int r) { #ifdef KEXI_MOBILE m_reportView->moveToPage(r + 1); #else // set in the navigator widget first, this will fix up the value it it's too small or large m_pageSelector->setCurrentRecordNumber(r + 1); m_reportView->moveToPage(m_pageSelector->currentRecordNumber()); #endif } int KexiReportView::currentRecord() const { return m_reportView->currentPage(); } int KexiReportView::recordCount() const { return m_reportView->pageCount(); } void KexiReportView::finishedAllASyncItems() { m_reportView->refreshCurrentPage(); } diff --git a/src/plugins/reports/kexireportview.h b/src/plugins/reports/kexireportview.h index bf213fa9f..ee4e1b168 100644 --- a/src/plugins/reports/kexireportview.h +++ b/src/plugins/reports/kexireportview.h @@ -1,92 +1,91 @@ /* * Kexi Report Plugin * Copyright (C) 2007-2008 by Adam Pigg (adam@piggz.co.uk) - * Copyright (C) 2014-2017 Jarosław Staniek + * Copyright (C) 2014-2018 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 * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #ifndef KEXIREPORTVIEW_H #define KEXIREPORTVIEW_H -#include - #include #include #include #include #include "kexireportpart.h" +class KexiDBReportDataSource; class KReportPreRenderer; class ORODocument; class KReportView; //! @todo KEXI3 class KexiScriptAdaptor; class KRScriptFunctions; #ifndef KEXI_MOBILE class KexiRecordNavigator; #endif /** */ class KexiReportView : public KexiView, public KexiRecordNavigatorHandler { Q_OBJECT public: explicit KexiReportView(QWidget *parent); ~KexiReportView(); virtual tristate afterSwitchFrom(Kexi::ViewMode mode); virtual tristate beforeSwitchTo(Kexi::ViewMode mode, bool *dontStore); virtual void addNewRecordRequested(); virtual void moveToFirstRecordRequested(); virtual void moveToLastRecordRequested(); virtual void moveToNextRecordRequested(); virtual void moveToPreviousRecordRequested(); virtual void moveToRecordRequested(int r); virtual int currentRecord() const; virtual int recordCount() const; private: KReportPreRenderer *m_preRenderer; KReportView *m_reportView; #ifndef KEXI_MOBILE KexiRecordNavigator *m_pageSelector; #endif KexiReportPartTempData* tempData() const; - KReportDataSource* createDataSource(const QDomElement &e); + KexiDBReportDataSource* createDataSource(const QDomElement &e); //! @todo KEXI3 KexiScriptAdaptor *m_kexi; KRScriptFunctions *m_functions; KReportRendererFactory m_factory; QUrl getExportUrl(const QString &mimetype, const QString &caption, const QString &lastExportPathOrVariable, const QString &extension); private Q_SLOTS: void slotPrintReport(); void slotExportAsPdf(); void slotExportAsWebPage(); #ifdef KEXI_SHOW_UNFINISHED void slotExportAsSpreadsheet(); void slotExportAsTextDocument(); #endif void openExportedDocument(const QUrl &destination); void finishedAllASyncItems(); }; #endif diff --git a/src/plugins/reports/krscriptfunctions.cpp b/src/plugins/reports/krscriptfunctions.cpp index 43254ac30..b311b7652 100644 --- a/src/plugins/reports/krscriptfunctions.cpp +++ b/src/plugins/reports/krscriptfunctions.cpp @@ -1,131 +1,82 @@ /* * Kexi Report Plugin * Copyright (C) 2007-2008 by Adam Pigg - * Copyright (C) 2012 Jarosław Staniek + * Copyright (C) 2012-2018 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 * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "krscriptfunctions.h" +#include "KexiDBReportDataSource.h" #include #include #include #include -KRScriptFunctions::KRScriptFunctions(const KReportDataSource* datasource, KDbConnection* conn) +KRScriptFunctions::KRScriptFunctions(KexiDBReportDataSource *datasource) + : m_dataSource(datasource) { - m_cursor = datasource; - m_connection = conn; - - if (datasource) { - if (m_connection->containsTable(datasource->sourceName()) == true) { - m_source = datasource->sourceName(); - } else if (m_connection->querySchema(datasource->sourceName())) { - KDbNativeStatementBuilder builder(conn, KDb::DriverEscaping); - KDbEscapedString source; - if (builder.generateSelectStatement(&source, m_connection->querySchema(datasource->sourceName()))) { - m_source = source.toByteArray(); - } - } - } + Q_ASSERT(m_dataSource); } KRScriptFunctions::~KRScriptFunctions() { } void KRScriptFunctions::setGroupData(const QMap& groupData) { m_groupData = groupData; } qreal KRScriptFunctions::math(const QString &function, const QString &field) { - QString ret = QLatin1String("0.0"); - - if (!m_connection) { - return 0.0; - } - - KDbEscapedString sql = KDbEscapedString("SELECT " + function + "(" + field + ") FROM (" + m_source + ")"); - - if (!m_groupData.isEmpty()) { - sql += " WHERE(" + where() + ')'; - } - - qDebug() << sql; - - m_connection->querySingleString(sql,&ret); - - return ret.toDouble(); + return m_dataSource->runAggregateFunction(function, field, m_groupData); } qreal KRScriptFunctions::sum(const QString &field) { return math("SUM", field); } qreal KRScriptFunctions::avg(const QString &field) { return math("AVG", field); } qreal KRScriptFunctions::min(const QString &field) { return math("MIN", field); } qreal KRScriptFunctions::max(const QString &field) { return math("MAX", field); } qreal KRScriptFunctions::count(const QString &field) { return math("COUNT", field); } QVariant KRScriptFunctions::value(const QString &field) { - QVariant val; - if (!m_cursor) { - qDebug() << "No cursor to get value of field " << field; - return val; - } - - QStringList fields = m_cursor->fieldNames(); - - val = m_cursor->value(fields.indexOf(field)); + const QVariant val = m_dataSource->value(field); if (val.type() == QVariant::String) { // UTF-8 values are expected so convert this return val.toString().toUtf8(); } - return val; } - -KDbEscapedString KRScriptFunctions::where() -{ - QByteArray w; - QMap::const_iterator i = m_groupData.constBegin(); - while (i != m_groupData.constEnd()) { - w += '(' + i.key().toUtf8() + QByteArrayLiteral(" = '") + i.value().toString().toUtf8() + QByteArrayLiteral("') AND "); - ++i; - } - w.chop(4); - //kreportDebug() << w; - return KDbEscapedString(w); -} diff --git a/src/plugins/reports/krscriptfunctions.h b/src/plugins/reports/krscriptfunctions.h index 89d44da46..f3af19b85 100644 --- a/src/plugins/reports/krscriptfunctions.h +++ b/src/plugins/reports/krscriptfunctions.h @@ -1,61 +1,63 @@ /* * Kexi Report Plugin * Copyright (C) 2007-2008 by Adam Pigg (adam@piggz.co.uk) + * Copyright (C) 2012-2018 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 * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #ifndef KRSCRIPTFUNCTIONS_H #define KRSCRIPTFUNCTIONS_H -#include #include #include +#include + +class KexiDBReportDataSource; class KDbConnection; class KDbCursor; /** */ class KRScriptFunctions : public KReportGroupTracker { Q_OBJECT public: - KRScriptFunctions(const KReportDataSource *, KDbConnection*); + KRScriptFunctions(KexiDBReportDataSource *dataSource); ~KRScriptFunctions(); private: - KDbConnection *m_connection; - const KReportDataSource *m_cursor; + KexiDBReportDataSource * const m_dataSource; QString m_source; + + //! @todo Move SQL aggregate functions to KDb qreal math(const QString &, const QString &); QMap m_groupData; - KDbEscapedString where(); - public Q_SLOTS: virtual void setGroupData(const QMap &groupData); qreal sum(const QString &); qreal avg(const QString &); qreal min(const QString &); qreal max(const QString &); qreal count(const QString &); QVariant value(const QString &); }; #endif