diff --git a/kexi/plugins/queries/kexiquerydesignerguieditor.cpp b/kexi/plugins/queries/kexiquerydesignerguieditor.cpp index 78b5a4f8390..b0ef057176e 100644 --- a/kexi/plugins/queries/kexiquerydesignerguieditor.cpp +++ b/kexi/plugins/queries/kexiquerydesignerguieditor.cpp @@ -1,1874 +1,1874 @@ /* This file is part of the KDE project Copyright (C) 2004 Lucijan Busch - Copyright (C) 2004-2015 Jarosław Staniek + Copyright (C) 2004-2016 Jarosław Staniek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kexiquerydesignerguieditor.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kexiquerypart.h" #include "kexiqueryview.h" #include #include #include //! @todo remove KEXI_NO_QUERY_TOTALS later #define KEXI_NO_QUERY_TOTALS //! indices for table columns #define COLUMN_ID_COLUMN 0 #define COLUMN_ID_TABLE 1 #define COLUMN_ID_VISIBLE 2 #ifdef KEXI_NO_QUERY_TOTALS # define COLUMN_ID_SORTING 3 # define COLUMN_ID_CRITERIA 4 #else # define COLUMN_ID_TOTALS 3 # define COLUMN_ID_SORTING 4 # define COLUMN_ID_CRITERIA 5 #endif /*! @internal */ class KexiQueryDesignerGuiEditor::Private { public: Private(KexiQueryDesignerGuiEditor *p) : q(p) { droppedNewRecord = 0; slotTableAdded_enabled = true; sortColumnPreferredWidth = 0; } bool changeSingleCellValue(KexiDB::RecordData &record, int columnNumber, const QVariant& value, KexiDB::ResultInfo* result) { data->clearRowEditBuffer(); if (!data->updateRowEditBuffer(&record, columnNumber, value) || !data->saveRowChanges(record, true)) { if (result) *result = data->result(); return false; } return true; } KexiQueryDesignerGuiEditor *q; KexiDB::TableViewData *data; KexiDataTableView *dataTable; QPointer conn; KexiRelationsView *relations; KexiSectionHeader *head; QSplitter *spl; /*! Used to remember in slotDroppedAtRow() what data was dropped, so we can create appropriate prop. set in slotRowInserted() This information is cached and entirely refreshed on updateColumnsData(). */ KexiDB::TableViewData *fieldColumnData, *tablesColumnData; /*! Collects identifiers selected in 1st (field) column, so we're able to distinguish between table identifiers selected from the dropdown list, and strings (e.g. expressions) entered by hand. This information is cached and entirely refreshed on updateColumnsData(). The dict is filled with (char*)1 values (doesn't matter what it is); */ QSet fieldColumnIdentifiers; void addFieldColumnIdentifier(const QString& id) { fieldColumnIdentifiers.insert(id.toLower()); } int comboArrowWidth; int sortColumnPreferredWidth; void initSortColumnPreferredWidth(const QVector &items) { int maxw = -1; foreach (const QString &text, items) { maxw = qMax(maxw, q->fontMetrics().width(text + " ")); } sortColumnPreferredWidth = maxw + KexiUtils::comboBoxArrowSize(q->style()).width(); } KexiDataAwarePropertySet* sets; KexiDB::RecordData *droppedNewRecord; QString droppedNewTable, droppedNewField; bool slotTableAdded_enabled; }; static bool isAsterisk(const QString& tableName, const QString& fieldName) { return tableName == "*" || fieldName.endsWith('*'); } //! @internal \return true if sorting is allowed for \a fieldName and \a tableName static bool sortingAllowed(const QString& fieldName, const QString& tableName) { return !(fieldName == "*" || (fieldName.isEmpty() && tableName == "*")); } //========================================================= KexiQueryDesignerGuiEditor::KexiQueryDesignerGuiEditor( QWidget *parent) : KexiView(parent) , d(new Private(this)) { d->conn = KexiMainWindowIface::global()->project()->dbConnection(); d->spl = new QSplitter(Qt::Vertical, this); d->spl->setChildrenCollapsible(false); d->relations = new KexiRelationsView(d->spl); d->spl->addWidget(d->relations); d->relations->setObjectName("relations"); connect(d->relations, SIGNAL(tableAdded(KexiDB::TableSchema&)), this, SLOT(slotTableAdded(KexiDB::TableSchema&))); connect(d->relations, SIGNAL(tableHidden(KexiDB::TableSchema&)), this, SLOT(slotTableHidden(KexiDB::TableSchema&))); connect(d->relations, SIGNAL(appendFields(KexiDB::TableOrQuerySchema&,QStringList)), this, SLOT(slotAppendFields(KexiDB::TableOrQuerySchema&,QStringList))); d->head = new KexiSectionHeader(i18n("Query Columns"), Qt::Vertical, d->spl); d->spl->addWidget(d->head); d->dataTable = new KexiDataTableView(d->head, false); d->head->setWidget(d->dataTable); d->dataTable->setObjectName("guieditor_dataTable"); d->dataTable->dataAwareObject()->setSpreadSheetMode(true); d->data = new KexiDB::TableViewData(); //just empty data d->sets = new KexiDataAwarePropertySet(this, d->dataTable->dataAwareObject()); connect(d->sets, SIGNAL(propertyChanged(KoProperty::Set&,KoProperty::Property&)), this, SLOT(slotPropertyChanged(KoProperty::Set&,KoProperty::Property&))); initTableColumns(); initTableRows(); QList c; c << COLUMN_ID_COLUMN << COLUMN_ID_TABLE << COLUMN_ID_CRITERIA; if (d->dataTable->tableView()/*sanity*/) { d->dataTable->tableView()->adjustColumnWidthToContents(COLUMN_ID_VISIBLE); d->dataTable->tableView()->setColumnWidth(COLUMN_ID_SORTING, d->sortColumnPreferredWidth); d->dataTable->tableView()->setStretchLastColumn(true); d->dataTable->tableView()->maximizeColumnsWidth(c); d->dataTable->tableView()->setDropsAtRowEnabled(true); connect(d->dataTable->tableView(), SIGNAL(dragOverRow(KexiDB::RecordData*,int,QDragMoveEvent*)), this, SLOT(slotDragOverTableRow(KexiDB::RecordData*,int,QDragMoveEvent*))); connect(d->dataTable->tableView(), SIGNAL(droppedAtRow(KexiDB::RecordData*,int,QDropEvent*,KexiDB::RecordData*&)), this, SLOT(slotDroppedAtRow(KexiDB::RecordData*,int,QDropEvent*,KexiDB::RecordData*&))); connect(d->dataTable->tableView(), SIGNAL(newItemAppendedForAfterDeletingInSpreadSheetMode()), this, SLOT(slotNewItemAppendedForAfterDeletingInSpreadSheetMode())); } connect(d->data, SIGNAL(aboutToChangeCell(KexiDB::RecordData*,int,QVariant&,KexiDB::ResultInfo*)), this, SLOT(slotBeforeCellChanged(KexiDB::RecordData*,int,QVariant&,KexiDB::ResultInfo*))); connect(d->data, SIGNAL(rowInserted(KexiDB::RecordData*,uint,bool)), this, SLOT(slotRowInserted(KexiDB::RecordData*,uint,bool))); connect(d->relations, SIGNAL(tablePositionChanged(KexiRelationsTableContainer*)), this, SLOT(slotTablePositionChanged(KexiRelationsTableContainer*))); connect(d->relations, SIGNAL(aboutConnectionRemove(KexiRelationsConnection*)), this, SLOT(slotAboutConnectionRemove(KexiRelationsConnection*))); addChildView(d->relations); addChildView(d->dataTable); setViewWidget(d->spl, false/* no focus proxy*/); setFocusProxy(d->dataTable); d->relations->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); d->head->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); updateGeometry(); d->spl->setSizes(QList() << 800 << 400); } KexiQueryDesignerGuiEditor::~KexiQueryDesignerGuiEditor() { delete d; } void KexiQueryDesignerGuiEditor::initTableColumns() { KexiDB::TableViewColumn *col1 = new KexiDB::TableViewColumn("column", KexiDB::Field::Enum, i18n("Column"), i18n("Describes field name or expression for the designed query.")); col1->setRelatedDataEditable(true); d->fieldColumnData = new KexiDB::TableViewData(KexiDB::Field::Text, KexiDB::Field::Text); col1->setRelatedData(d->fieldColumnData); d->data->addColumn(col1); KexiDB::TableViewColumn *col2 = new KexiDB::TableViewColumn("table", KexiDB::Field::Enum, i18n("Table"), i18n("Describes table for a given field. Can be empty.")); d->tablesColumnData = new KexiDB::TableViewData(KexiDB::Field::Text, KexiDB::Field::Text); col2->setRelatedData(d->tablesColumnData); d->data->addColumn(col2); KexiDB::TableViewColumn *col3 = new KexiDB::TableViewColumn("visible", KexiDB::Field::Boolean, i18n("Visible"), i18n("Describes visibility for a given field or expression.")); col3->field()->setDefaultValue(QVariant(false)); col3->field()->setNotNull(true); d->data->addColumn(col3); #ifndef KEXI_NO_QUERY_TOTALS KexiDB::TableViewColumn *col4 = new KexiDB::TableViewColumn("totals", KexiDB::Field::Enum, futureI18n("Totals"), futureI18n("Describes a way of computing totals for a given field or expression.")); QVector totalsTypes; totalsTypes.append(futureI18n("Group by")); totalsTypes.append(futureI18n("Sum")); totalsTypes.append(futureI18n("Average")); totalsTypes.append(futureI18n("Min")); totalsTypes.append(futureI18n("Max")); //! @todo more like this col4->field()->setEnumHints(totalsTypes); d->data->addColumn(col4); #endif KexiDB::TableViewColumn *col5 = new KexiDB::TableViewColumn("sort", KexiDB::Field::Enum, i18n("Sorting"), i18n("Describes a way of sorting for a given field.")); QVector sortTypes; sortTypes.append(""); sortTypes.append(i18n("Ascending")); sortTypes.append(i18n("Descending")); col5->field()->setEnumHints(sortTypes); d->data->addColumn(col5); d->initSortColumnPreferredWidth(sortTypes); KexiDB::TableViewColumn *col6 = new KexiDB::TableViewColumn("criteria", KexiDB::Field::Text, i18n("Criteria"), i18n("Describes the criteria for a given field or expression.")); d->data->addColumn(col6); } void KexiQueryDesignerGuiEditor::initTableRows() { d->data->deleteAllRows(); for (int i = 0; i < (int)d->sets->size(); i++) { KexiDB::RecordData* record; d->data->append(record = d->data->createItem()); (*record)[COLUMN_ID_VISIBLE] = QVariant(false); } d->dataTable->dataAwareObject()->setData(d->data); updateColumnsData(); } void KexiQueryDesignerGuiEditor::updateColumnsData() { d->dataTable->dataAwareObject()->acceptRowEdit(); QStringList sortedTableNames; foreach(KexiRelationsTableContainer* cont, *d->relations->tables()) { sortedTableNames += cont->schema()->name(); } qSort(sortedTableNames); //several tables can be hidden now, so remove rows for these tables QList rowsToDelete; for (int r = 0; r < (int)d->sets->size(); r++) { KoProperty::Set *set = d->sets->at(r); if (set) { QString tableName = (*set)["table"].value().toString(); QString fieldName = (*set)["field"].value().toString(); const bool allTablesAsterisk = tableName == "*" && d->relations->tables()->isEmpty(); const bool fieldNotFound = tableName != "*" && !(*set)["isExpression"].value().toBool() && sortedTableNames.end() == qFind(sortedTableNames.begin(), sortedTableNames.end(), tableName); if (allTablesAsterisk || fieldNotFound) { //table not found: mark this line for later removal rowsToDelete += r; } } } d->data->deleteRows(rowsToDelete); //update 'table' and 'field' columns d->tablesColumnData->deleteAllRows(); d->fieldColumnData->deleteAllRows(); d->fieldColumnIdentifiers.clear(); KexiDB::RecordData *record = d->fieldColumnData->createItem(); (*record)[COLUMN_ID_COLUMN] = "*"; (*record)[COLUMN_ID_TABLE] = "*"; d->fieldColumnData->append(record); d->addFieldColumnIdentifier((*record)[COLUMN_ID_COLUMN].toString()); //cache tempData()->unregisterForTablesSchemaChanges(); foreach(const QString& tableName, sortedTableNames) { //table /*! @todo what about query? */ KexiDB::TableSchema *table = d->relations->tables()->value(tableName)->schema()->table(); d->conn->registerForTableSchemaChanges(*tempData(), *table); //this table will be used record = d->tablesColumnData->createItem(); (*record)[COLUMN_ID_COLUMN] = table->name(); (*record)[COLUMN_ID_TABLE] = (*record)[COLUMN_ID_COLUMN]; d->tablesColumnData->append(record); //fields record = d->fieldColumnData->createItem(); (*record)[COLUMN_ID_COLUMN] = QString(table->name() + ".*"); (*record)[COLUMN_ID_TABLE] = (*record)[COLUMN_ID_COLUMN]; d->fieldColumnData->append(record); d->addFieldColumnIdentifier((*record)[COLUMN_ID_COLUMN].toString()); //cache foreach(KexiDB::Field *field, *table->fields()) { record = d->fieldColumnData->createItem(); (*record)[COLUMN_ID_COLUMN] = QString(table->name() + '.' + field->name()); (*record)[COLUMN_ID_TABLE] = QString(" " + field->name()); d->fieldColumnData->append(record); d->addFieldColumnIdentifier((*record)[COLUMN_ID_COLUMN].toString()); //cache } } //! @todo } KexiRelationsView *KexiQueryDesignerGuiEditor::relationsView() const { return d->relations; } KexiQueryPart::TempData * KexiQueryDesignerGuiEditor::tempData() const { return static_cast(window()->data()); } static QString msgCannotSwitch_EmptyDesign() { return i18n("Cannot switch to data view, because query design is empty.\n" "First, please create your design."); } bool KexiQueryDesignerGuiEditor::buildSchema(QString *errMsg) { //build query schema KexiQueryPart::TempData * temp = tempData(); if (temp->query()) { KexiView *queryDataView = window()->viewForMode(Kexi::DataViewMode); if (queryDataView) { dynamic_cast(queryDataView)->setData(0); } temp->clearQuery(); } else { temp->setQuery(new KexiDB::QuerySchema()); } //add tables foreach(KexiRelationsTableContainer* cont, *d->relations->tables()) { /*! @todo what about query? */ temp->query()->addTable(cont->schema()->table()); } //add fields, also build: // -WHERE expression // -ORDER BY list KexiDB::BaseExpr *whereExpr = 0; const uint count = qMin(d->data->count(), d->sets->size()); bool fieldsFound = false; KexiDB::TableViewData::Iterator it(d->data->constBegin()); for (uint i = 0; i < count && it != d->data->constEnd(); ++it, i++) { if (!(**it)[COLUMN_ID_TABLE].isNull() && (**it)[COLUMN_ID_COLUMN].isNull()) { //show message about missing field name, and set focus to that cell kDebug() << "no field provided!"; d->dataTable->dataAwareObject()->setCursorPosition(i, 0); if (errMsg) *errMsg = i18n("Select column for table \"%1\"", (**it)[COLUMN_ID_TABLE].toString()); return false; } KoProperty::Set *set = d->sets->at(i); if (set) { QString tableName = (*set)["table"].value().toString().trimmed(); QString fieldName = (*set)["field"].value().toString(); QString fieldAndTableName = fieldName; KexiDB::Field *currentField = 0; // will be set if this column is a single field if (!tableName.isEmpty()) fieldAndTableName.prepend(tableName + "."); const bool fieldVisible = (*set)["visible"].value().toBool(); QString criteriaStr = (*set)["criteria"].value().toString(); QByteArray alias((*set)["alias"].value().toByteArray()); if (!criteriaStr.isEmpty()) { int token; KexiDB::BaseExpr *criteriaExpr = parseExpressionString(criteriaStr, token, true/*allowRelationalOperator*/); if (!criteriaExpr) {//for sanity if (errMsg) *errMsg = i18n("Invalid criteria \"%1\"", criteriaStr); delete whereExpr; return false; } //build relational expression for column variable KexiDB::VariableExpr *varExpr = new KexiDB::VariableExpr(fieldAndTableName); criteriaExpr = new KexiDB::BinaryExpr(KexiDBExpr_Relational, varExpr, token, criteriaExpr); //critera ok: add it to WHERE section if (whereExpr) whereExpr = new KexiDB::BinaryExpr(KexiDBExpr_Logical, whereExpr, AND, criteriaExpr); else //first expr. whereExpr = criteriaExpr; } if (tableName.isEmpty()) { if ((*set)["isExpression"].value().toBool() == true) { //add expression column int dummyToken; KexiDB::BaseExpr *columnExpr = parseExpressionString(fieldName, dummyToken, false/*!allowRelationalOperator*/); if (!columnExpr) { if (errMsg) *errMsg = i18n("Invalid expression \"%1\"", fieldName); return false; } temp->query()->addExpression(columnExpr, fieldVisible); if (fieldVisible) fieldsFound = true; if (!alias.isEmpty()) temp->query()->setColumnAlias(temp->query()->fieldCount() - 1, alias); } //! @todo } else if (tableName == "*") { //all tables asterisk temp->query()->addAsterisk(new KexiDB::QueryAsterisk(temp->query(), 0), fieldVisible); if (fieldVisible) fieldsFound = true; continue; } else { KexiDB::TableSchema *t = d->conn->tableSchema(tableName); if (fieldName == "*") { //single-table asterisk: + ".*" + number temp->query()->addAsterisk(new KexiDB::QueryAsterisk(temp->query(), t), fieldVisible); if (fieldVisible) fieldsFound = true; } else { if (!t) { kWarning() << "query designer: NO TABLE '" << (*set)["table"].value().toString() << "'"; continue; } currentField = t->field(fieldName); if (!currentField) { kWarning() << "query designer: NO FIELD '" << fieldName << "'"; continue; } if (!fieldVisible && criteriaStr.isEmpty() && set->contains("isExpression") && (*set)["sorting"].value().toString() != "nosorting") { kDebug() << "invisible field with sorting: do not add it to the fields list"; continue; } const int tablePosition = temp->query()->tablePosition(t->name()); temp->query()->addField(currentField, tablePosition, fieldVisible); if (fieldVisible) fieldsFound = true; if (!alias.isEmpty()) temp->query()->setColumnAlias(temp->query()->fieldCount() - 1, alias); } } } else {//!set //kDebug() << (**it)[COLUMN_ID_TABLE].toString(); } } if (!fieldsFound) { if (errMsg) *errMsg = msgCannotSwitch_EmptyDesign(); return false; } if (whereExpr) kDebug() << "setting CRITERIA:" << whereExpr->debugString(); //set always, because if whereExpr==NULL, //this will clear prev. expr temp->query()->setWhereExpression(whereExpr); //add relations (looking for connections) foreach(KexiRelationsConnection* conn, *d->relations->connections()) { KexiRelationsTableContainer *masterTable = conn->masterTable(); KexiRelationsTableContainer *detailsTable = conn->detailsTable(); /*! @todo what about query? */ temp->query()->addRelationship( masterTable->schema()->table()->field(conn->masterField()), detailsTable->schema()->table()->field(conn->detailsField())); } // Add sorting information (ORDER BY) - we can do that only now // after all QueryColumnInfo items are instantiated KexiDB::OrderByColumnList orderByColumns; it = d->data->constBegin(); int fieldNumber = -1; //field number (empty rows are omitted) for (uint i = 0/*row number*/; i < count && it != d->data->constEnd(); ++it, i++) { KoProperty::Set *set = d->sets->at(i); if (!set) continue; fieldNumber++; KexiDB::Field *currentField = 0; KexiDB::QueryColumnInfo *currentColumn = 0; QString sortingString((*set)["sorting"].value().toString()); if (sortingString != "ascending" && sortingString != "descending") continue; if (!(*set)["visible"].value().toBool()) { // this row defines invisible field but contains sorting information, // what means KexiDB::Field should be used as a reference for this sorting // Note1: alias is not supported here. // Try to find a field (not mentioned after SELECT): currentField = temp->query()->findTableField((*set)["field"].value().toString()); if (!currentField) { kWarning() << "NO FIELD" << (*set)["field"].value().toString() << "available for sorting"; continue; } orderByColumns.appendField(*currentField, sortingString == "ascending"); continue; } currentField = temp->query()->field((uint)fieldNumber); if (!currentField || currentField->isExpression() || currentField->isQueryAsterisk()) //! @todo support expressions here continue; //! @todo ok, but not for expressions QString aliasString((*set)["alias"].value().toString()); currentColumn = temp->query()->columnInfo( (*set)["table"].value().toString() + "." + (aliasString.isEmpty() ? currentField->name() : aliasString)); if (currentField && currentColumn) { if (currentColumn->visible) orderByColumns.appendColumn(*currentColumn, sortingString == "ascending"); else if (currentColumn->field) orderByColumns.appendField(*currentColumn->field, sortingString == "ascending"); } } temp->query()->setOrderByColumnList(orderByColumns); temp->query()->debug(); temp->registerTableSchemaChanges(temp->query()); //! @todo ? return true; } tristate KexiQueryDesignerGuiEditor::beforeSwitchTo(Kexi::ViewMode mode, bool &dontStore) { kDebug() << mode; if (!d->dataTable->dataAwareObject()->acceptRowEdit()) return cancelled; - kDebug() << "queryChangedInPreviousView:" << tempData()->queryChangedInPreviousView(); + kDebug() << "queryChangedInView:" << tempData()->queryChangedInView(); if (mode == Kexi::DesignViewMode) { return true; } else if (mode == Kexi::DataViewMode) { if (!isDirty() && window()->neverSaved()) { KMessageBox::information(this, msgCannotSwitch_EmptyDesign()); return cancelled; } - if (tempData()->queryChangedInPreviousView() || !tempData()->query()) { + if (tempData()->queryChangedInView() != Kexi::NoViewMode || !tempData()->query()) { //remember current design in a temporary structure QString errMsg; //build schema; problems are not allowed if (!buildSchema(&errMsg)) { KMessageBox::sorry(this, errMsg); return cancelled; } } dontStore = true; //! @todo return true; } else if (mode == Kexi::TextViewMode) { dontStore = true; - if (tempData()->queryChangedInPreviousView() || !tempData()->query()) { + if (tempData()->queryChangedInView() != Kexi::NoViewMode || !tempData()->query()) { //remember current design in a temporary structure //build schema; ignore problems buildSchema(); } /* if (tempData()->query && tempData()->query->fieldCount()==0) { //no fields selected: let's add "*" (all-tables asterisk), // otherwise SQL statement will be invalid tempData()->query->addAsterisk( new KexiDB::QueryAsterisk( tempData()->query ) ); }*/ //! @todo return true; } return false; } tristate KexiQueryDesignerGuiEditor::afterSwitchFrom(Kexi::ViewMode mode) { if (mode == Kexi::NoViewMode || (mode == Kexi::DataViewMode && !tempData()->query())) { //this is not a SWITCH but a fresh opening in this view mode if (!window()->neverSaved()) { if (!loadLayout()) { //err msg window()->setStatus(d->conn, i18n("Query definition loading failed."), i18n("Query design may be corrupted so it could not be opened even in text view.\n" "You can delete the query and create it again.")); return false; } // Invalid queries case: // KexiWindow::switchToViewMode() first opens DesignViewMode, // and then KexiQueryPart::loadSchemaData() doesn't allocate QuerySchema object // do we're carefully looking at window()->schemaData() KexiDB::QuerySchema * q = dynamic_cast(window()->schemaData()); if (q) { KexiDB::ResultInfo result; showFieldsForQuery(q, result); if (!result.success) { window()->setStatus(&result, i18n("Query definition loading failed.")); tempData()->proposeOpeningInTextViewModeBecauseOfProblems = true; return false; } } //! @todo load global query properties } } else if (mode == Kexi::TextViewMode || mode == Kexi::DataViewMode) { // Switch from text or data view. In the second case, the design could be changed as well // because there could be changes made in the text view before switching to the data view. - if (tempData()->queryChangedInPreviousView()) { - //previous view changed query data + if (tempData()->queryChangedInView() == Kexi::TextViewMode) { + //SQL view changed the query design //-clear and regenerate GUI items initTableRows(); //! @todo if (tempData()->query()) { //there is a query schema to show showTablesForQuery(tempData()->query()); //-show fields KexiDB::ResultInfo result; showFieldsAndRelationsForQuery(tempData()->query(), result); if (!result.success) { window()->setStatus(&result, i18n("Query definition loading failed.")); return false; } } else { d->relations->clear(); } } //! @todo load global query properties } if (mode == Kexi::DataViewMode) { //this is just a SWITCH from data view //set cursor if needed: if (d->dataTable->dataAwareObject()->currentRow() < 0 || d->dataTable->dataAwareObject()->currentColumn() < 0) { d->dataTable->dataAwareObject()->ensureCellVisible(0, 0); d->dataTable->dataAwareObject()->setCursorPosition(0, 0); } } if (d->sets->size() > 0) { d->dataTable->tableView()->adjustColumnWidthToContents(COLUMN_ID_COLUMN); d->dataTable->tableView()->adjustColumnWidthToContents(COLUMN_ID_TABLE); } - tempData()->setQueryChangedInPreviousView(false); + tempData()->setQueryChangedInView(false); setFocus(); //to allow shared actions proper update return true; } KexiDB::SchemaData* KexiQueryDesignerGuiEditor::storeNewData(const KexiDB::SchemaData& sdata, KexiView::StoreNewDataOptions options, bool &cancel) { Q_UNUSED(options); if (!d->dataTable->dataAwareObject()->acceptRowEdit()) { cancel = true; return 0; } QString errMsg; KexiQueryPart::TempData * temp = tempData(); - if (!temp->query() || !(viewMode() == Kexi::DesignViewMode && !temp->queryChangedInPreviousView())) { + if (!temp->query() || !(viewMode() == Kexi::DesignViewMode && temp->queryChangedInView() == Kexi::NoViewMode)) { //only rebuild schema if it has not been rebuilt previously if (!buildSchema(&errMsg)) { KMessageBox::sorry(this, errMsg); cancel = true; return 0; } } (KexiDB::SchemaData&)*temp->query() = sdata; //copy main attributes bool ok = d->conn->storeObjectSchemaData( *temp->query(), true /*newObject*/); if (ok) { ok = KexiMainWindowIface::global()->project()->removeUserDataBlock(temp->query()->id()); // for sanity } window()->setId(temp->query()->id()); if (ok) ok = storeLayout(); if (!ok) { temp->setQuery(0); return 0; } return temp->takeQuery(); //will be returned, so: don't keep it in temp } tristate KexiQueryDesignerGuiEditor::storeData(bool dontAsk) { if (!d->dataTable->dataAwareObject()->acceptRowEdit()) return cancelled; const bool was_dirty = isDirty(); tristate res = KexiView::storeData(dontAsk); //this clears dirty flag if (true == res) res = buildSchema(); if (true == res) res = storeLayout(); if (true != res) { if (was_dirty) setDirty(true); } return res; } void KexiQueryDesignerGuiEditor::showTablesForQuery(KexiDB::QuerySchema *query) { // instead of hiding all tables and showing some tables, // show only these new and hide these unncecessary; the same for connections) d->slotTableAdded_enabled = false; //speedup d->relations->removeAllConnections(); //connections will be recreated d->relations->hideAllTablesExcept(query->tables()); foreach(KexiDB::TableSchema* table, *query->tables()) { d->relations->addTable(table); } d->slotTableAdded_enabled = true; updateColumnsData(); } void KexiQueryDesignerGuiEditor::addConnection( KexiDB::Field *masterField, KexiDB::Field *detailsField) { SourceConnection conn; conn.masterTable = masterField->table()->name(); //<<name(); conn.detailsTable = detailsField->table()->name(); conn.detailsField = detailsField->name(); d->relations->addConnection(conn); } void KexiQueryDesignerGuiEditor::showFieldsForQuery(KexiDB::QuerySchema *query, KexiDB::ResultInfo& result) { showFieldsOrRelationsForQueryInternal(query, true, false, result); } void KexiQueryDesignerGuiEditor::showRelationsForQuery(KexiDB::QuerySchema *query, KexiDB::ResultInfo& result) { showFieldsOrRelationsForQueryInternal(query, false, true, result); } void KexiQueryDesignerGuiEditor::showFieldsAndRelationsForQuery(KexiDB::QuerySchema *query, KexiDB::ResultInfo& result) { showFieldsOrRelationsForQueryInternal(query, true, true, result); } void KexiQueryDesignerGuiEditor::showFieldsOrRelationsForQueryInternal( KexiDB::QuerySchema *query, bool showFields, bool showRelations, KexiDB::ResultInfo& result) { result.clear(); const bool was_dirty = isDirty(); //1. Show explicitly declared relations: if (showRelations) { foreach(KexiDB::Relationship *rel, *query->relationships()) { //! @todo: now only sigle-field relationships are implemented! KexiDB::Field *masterField = rel->masterIndex()->fields()->first(); KexiDB::Field *detailsField = rel->detailsIndex()->fields()->first(); addConnection(masterField, detailsField); } } //2. Collect information about criterias // --this must be top level chain of AND's // --this will also show joins as: [table1.]field1 = [table2.]field2 KexiUtils::CaseInsensitiveHash criterias; KexiDB::BaseExpr* e = query->whereExpression(); KexiDB::BaseExpr* eItem = 0; while (e) { //eat parentheses because the expression can be (....) AND (... AND ... ) while (e && e->toUnary() && e->token() == '(') e = e->toUnary()->arg(); if (e->toBinary() && e->token() == AND) { eItem = e->toBinary()->left(); e = e->toBinary()->right(); } else { eItem = e; e = 0; } //eat parentheses while (eItem && eItem->toUnary() && eItem->token() == '(') eItem = eItem->toUnary()->arg(); if (!eItem) continue; kDebug() << eItem->toString(0); KexiDB::BinaryExpr* binary = eItem->toBinary(); if (binary && eItem->exprClass() == KexiDBExpr_Relational) { KexiDB::Field *leftField = 0, *rightField = 0; if (eItem->token() == '=' && binary->left()->toVariable() && binary->right()->toVariable() && (leftField = query->findTableField(binary->left()->toString(0))) && (rightField = query->findTableField(binary->right()->toString(0)))) { //! @todo move this check to parser on QuerySchema creation //! or to QuerySchema creation (WHERE expression should be then simplified //! by removing joins //this is relationship defined as following JOIN: [table1.]field1 = [table2.]field2 if (showRelations) { //! @todo testing primary key here is too simplified; maybe look ar isForeignKey() or indices.. //! @todo what about multifield joins? if (leftField->isPrimaryKey()) addConnection(leftField /*master*/, rightField /*details*/); else addConnection(rightField /*master*/, leftField /*details*/); //! @todo addConnection() should have "bool oneToOne" arg, for 1-to-1 relations } } else if (binary->left()->toVariable()) { //this is: variable , op , argument //store variable -> argument: criterias.insertMulti(binary->left()->toVariable()->name, binary->right()); } else if (binary->right()->toVariable()) { //this is: argument , op , variable //store variable -> argument: criterias.insertMulti(binary->right()->toVariable()->name, binary->left()); } } } //while if (!showFields) return; //3. show fields (including * and table.*) uint row_num = 0; QSet usedCriterias; // <-- used criterias will be saved here // so in step 4. we will be able to add // remaining invisible columns with criterias query->debug(); foreach(KexiDB::Field* field, *query->fields()) { field->debug(); } foreach(KexiDB::Field* field, *query->fields()) { //append a new row QString tableName, fieldName, columnAlias, criteriaString; KexiDB::BinaryExpr *criteriaExpr = 0; KexiDB::BaseExpr *criteriaArgument = 0; if (field->isQueryAsterisk()) { if (field->table()) {//single-table asterisk tableName = field->table()->name(); fieldName = "*"; } else {//all-tables asterisk tableName = "*"; fieldName = ""; } } else { columnAlias = query->columnAlias(row_num); if (field->isExpression()) { //! @todo ok? perhaps do not allow to omit aliases? fieldName = field->expression()->toString(0); } else { tableName = field->table()->name(); fieldName = field->name(); criteriaArgument = criterias.value(fieldName); if (!criteriaArgument) {//try table.field criteriaArgument = criterias.value(tableName + "." + fieldName); } if (criteriaArgument) {//criteria expression is just a parent of argument criteriaExpr = criteriaArgument->parent()->toBinary(); usedCriterias.insert(criteriaArgument); //save info. about used criteria } } } //create new row data KexiDB::RecordData *newRecord = createNewRow(tableName, fieldName, true /* visible*/); if (criteriaExpr) { //! @todo fix for !INFIX operators if (criteriaExpr->token() == '=') criteriaString = criteriaArgument->toString(0); else criteriaString = criteriaExpr->tokenToString(0) + " " + criteriaArgument->toString(0); (*newRecord)[COLUMN_ID_CRITERIA] = criteriaString; } d->dataTable->dataAwareObject()->insertItem(newRecord, row_num); //OK, row inserted: create a new set for it KoProperty::Set &set = *createPropertySet(row_num, tableName, fieldName, true/*new one*/); if (!columnAlias.isEmpty()) set["alias"].setValue(columnAlias, false); if (!criteriaString.isEmpty()) set["criteria"].setValue(criteriaString, false); if (field->isExpression()) { if (!d->changeSingleCellValue(*newRecord, COLUMN_ID_COLUMN, QVariant(columnAlias + ": " + field->expression()->toString(0)), &result)) return; //problems with setting column expression } row_num++; } //4. show ORDER BY information d->data->clearRowEditBuffer(); KexiDB::OrderByColumnList& orderByColumns = query->orderByColumnList(); QHash columnsOrder( query->columnsOrder(KexiDB::QuerySchema::UnexpandedListWithoutAsterisks)); for (KexiDB::OrderByColumn::ListConstIterator orderByColumnIt(orderByColumns.constBegin()); orderByColumnIt != orderByColumns.constEnd(); ++orderByColumnIt) { KexiDB::OrderByColumn* orderByColumn = *orderByColumnIt; KexiDB::QueryColumnInfo *column = orderByColumn->column(); KexiDB::RecordData *record = 0; KoProperty::Set *rowPropertySet = 0; if (column) { //sorting for visible column if (column->visible) { if (columnsOrder.contains(column)) { const int columnPosition = columnsOrder.value(column); record = d->data->at(columnPosition); rowPropertySet = d->sets->at(columnPosition); kDebug() << "\tSetting \"" << orderByColumn->debugString() << "\" sorting for record #" << columnPosition; } } } else if (orderByColumn->field()) { //this will be presented as invisible field: create new row KexiDB::Field* field = orderByColumn->field(); QString tableName(field->table() ? field->table()->name() : QString()); record = createNewRow(tableName, field->name(), false /* !visible*/); d->dataTable->dataAwareObject()->insertItem(record, row_num); rowPropertySet = createPropertySet(row_num, tableName, field->name(), true /*newOne*/); propertySetSwitched(); kDebug() << "\tSetting \"" << orderByColumn->debugString() << "\" sorting for invisible field" << field->name() << ", table " << tableName << " -row #" << row_num; row_num++; } //alter sorting for either existing or new row if (record && rowPropertySet) { // this will automatically update "sorting" property d->data->updateRowEditBuffer(record, COLUMN_ID_SORTING, orderByColumn->ascending() ? 1 : 2); // in slotBeforeCellChanged() d->data->saveRowChanges(*record, true); (*rowPropertySet)["sorting"].clearModifiedFlag(); // this property should look "fresh" if (!(*record)[COLUMN_ID_VISIBLE].toBool()) //update (*rowPropertySet)["visible"].setValue(QVariant(false), false/*rememberOldValue*/); } } //5. Show fields for unused criterias (with "Visible" column set to false) foreach( KexiDB::BaseExpr *criteriaArgument, // <-- contains field or table.field criterias) { if (usedCriterias.contains(criteriaArgument)) continue; //unused: append a new row KexiDB::BinaryExpr *criteriaExpr = criteriaArgument->parent()->toBinary(); if (!criteriaExpr) { kWarning() << "criteriaExpr is not a binary expr"; continue; } KexiDB::VariableExpr *columnNameArgument = criteriaExpr->left()->toVariable(); //left or right if (!columnNameArgument) { columnNameArgument = criteriaExpr->right()->toVariable(); if (!columnNameArgument) { kWarning() << "columnNameArgument is not a variable (table or table.field) expr"; continue; } } KexiDB::Field* field = 0; if (!columnNameArgument->name.contains('.') && query->tables()->count() == 1) { //extreme case: only field name provided for one-table query: field = query->tables()->first()->field(columnNameArgument->name); } else { field = query->findTableField(columnNameArgument->name); } if (!field) { kWarning() << "no columnInfo found in the query for name" << columnNameArgument->name; continue; } QString tableName, fieldName, columnAlias, criteriaString; //! @todo what about ALIAS? tableName = field->table()->name(); fieldName = field->name(); //create new row data KexiDB::RecordData *newRecord = createNewRow(tableName, fieldName, false /* !visible*/); if (criteriaExpr) { //! @todo fix for !INFIX operators if (criteriaExpr->token() == '=') criteriaString = criteriaArgument->toString(0); else criteriaString = criteriaExpr->tokenToString(0) + " " + criteriaArgument->toString(0); (*newRecord)[COLUMN_ID_CRITERIA] = criteriaString; } d->dataTable->dataAwareObject()->insertItem(newRecord, row_num); //OK, row inserted: create a new set for it KoProperty::Set &set = *createPropertySet(row_num++, tableName, fieldName, true/*new one*/); //! @todo if (!columnAlias.isEmpty()) //! @todo set["alias"].setValue(columnAlias, false); //// if (!criteriaString.isEmpty()) set["criteria"].setValue(criteriaString, false); set["visible"].setValue(QVariant(false), false); } //current property set has most probably changed propertySetSwitched(); if (!was_dirty) setDirty(false); //move to 1st column, 1st row d->dataTable->dataAwareObject()->ensureCellVisible(0, 0); // tempData()->registerTableSchemaChanges(query); } bool KexiQueryDesignerGuiEditor::loadLayout() { QString xml; //! @todo if (!loadDataBlock( xml, "query_layout" )) { loadDataBlock(xml, "query_layout"); //! @todo errmsg if (xml.isEmpty()) { //in a case when query layout was not saved, build layout by hand // -- dynamic cast because of a need for handling invalid queries // (as in KexiQueryDesignerGuiEditor::afterSwitchFrom()): KexiDB::QuerySchema * q = dynamic_cast(window()->schemaData()); if (q) { showTablesForQuery(q); KexiDB::ResultInfo result; showRelationsForQuery(q, result); if (!result.success) { window()->setStatus(&result, i18n("Query definition loading failed.")); return false; } } return true; } QDomDocument doc; doc.setContent(xml); QDomElement doc_el = doc.documentElement(), el; if (doc_el.tagName() != "query_layout") { //! @todo errmsg return false; } const bool was_dirty = isDirty(); //add tables and relations to the relation view for (el = doc_el.firstChild().toElement(); !el.isNull(); el = el.nextSibling().toElement()) { if (el.tagName() == "table") { KexiDB::TableSchema *t = d->conn->tableSchema(el.attribute("name")); int x = el.attribute("x", "-1").toInt(); int y = el.attribute("y", "-1").toInt(); int width = el.attribute("width", "-1").toInt(); int height = el.attribute("height", "-1").toInt(); QRect rect; if (x != -1 || y != -1 || width != -1 || height != -1) rect = QRect(x, y, width, height); d->relations->addTable(t, rect); } else if (el.tagName() == "conn") { SourceConnection src_conn; src_conn.masterTable = el.attribute("mtable"); src_conn.masterField = el.attribute("mfield"); src_conn.detailsTable = el.attribute("dtable"); src_conn.detailsField = el.attribute("dfield"); d->relations->addConnection(src_conn); } } if (!was_dirty) setDirty(false); return true; } bool KexiQueryDesignerGuiEditor::storeLayout() { KexiQueryPart::TempData * temp = tempData(); // Save SQL without driver-escaped keywords. if (window()->schemaData()) //set this instance as obsolete (only if it's stored) d->conn->setQuerySchemaObsolete(window()->schemaData()->name()); KexiDB::Connection::SelectStatementOptions options; options.identifierEscaping = KexiDB::Driver::EscapeKexi | KexiDB::Driver::EscapeAsNecessary; options.addVisibleLookupColumns = false; QString sqlText = KexiDB::selectStatement(0, *temp->query(), options); if (!storeDataBlock(sqlText, "sql")) { return false; } //serialize detailed XML query definition QString xml = "", tmp; foreach(KexiRelationsTableContainer* cont, *d->relations->tables()) { /*! @todo what about query? */ tmp = QString("schema()->name()) + "\" x=\"" + QString::number(cont->x()) + "\" y=\"" + QString::number(cont->y()) + "\" width=\"" + QString::number(cont->width()) + "\" height=\"" + QString::number(cont->height()) + "\"/>"; xml += tmp; } foreach(KexiRelationsConnection *conn, *d->relations->connections()) { tmp = QString("masterTable()->schema()->name()) + "\" mfield=\"" + conn->masterField() + "\" dtable=\"" + QString(conn->detailsTable()->schema()->name()) + "\" dfield=\"" + conn->detailsField() + "\"/>"; xml += tmp; } xml += ""; if (!storeDataBlock(xml, "query_layout")) { return false; } return true; } QSize KexiQueryDesignerGuiEditor::sizeHint() const { QSize s1 = d->relations->sizeHint(); QSize s2 = d->head->sizeHint(); return QSize(qMax(s1.width(), s2.width()), s1.height() + s2.height()); } KexiDB::RecordData* KexiQueryDesignerGuiEditor::createNewRow(const QString& tableName, const QString& fieldName, bool visible) const { KexiDB::RecordData *newRecord = d->data->createItem(); QString key; if (tableName == "*") key = "*"; else { if (!tableName.isEmpty()) key = (tableName + "."); key += fieldName; } (*newRecord)[COLUMN_ID_COLUMN] = key; (*newRecord)[COLUMN_ID_TABLE] = tableName; (*newRecord)[COLUMN_ID_VISIBLE] = QVariant(visible); #ifndef KEXI_NO_QUERY_TOTALS (*newRecord)[COLUMN_ID_TOTALS] = QVariant(0); #endif return newRecord; } void KexiQueryDesignerGuiEditor::slotDragOverTableRow( KexiDB::RecordData * /*record*/, int /*row*/, QDragMoveEvent* e) { if (e->provides("kexi/field")) { e->setAccepted(true); } } void KexiQueryDesignerGuiEditor::slotDroppedAtRow(KexiDB::RecordData * /*record*/, int /*row*/, QDropEvent *ev, KexiDB::RecordData*& newRecord) { QString sourcePartClass; QString srcTable; QStringList srcFields; if (!KexiFieldDrag::decode(ev, &sourcePartClass, &srcTable, &srcFields)) return; if (srcFields.count() != 1) { return; } //insert new row at specific place newRecord = createNewRow(srcTable, srcFields[0], true /* visible*/); d->droppedNewRecord = newRecord; d->droppedNewTable = srcTable; d->droppedNewField = srcFields[0]; //! @todo } void KexiQueryDesignerGuiEditor::slotNewItemAppendedForAfterDeletingInSpreadSheetMode() { KexiDB::RecordData *record = d->data->last(); if (record) (*record)[COLUMN_ID_VISIBLE] = QVariant(false); //the same init as in initTableRows() } void KexiQueryDesignerGuiEditor::slotRowInserted(KexiDB::RecordData* record, uint row, bool /*repaint*/) { if (d->droppedNewRecord && d->droppedNewRecord == record) { createPropertySet(row, d->droppedNewTable, d->droppedNewField, true); propertySetSwitched(); d->droppedNewRecord = 0; } - tempData()->setQueryChangedInPreviousView(true); + tempData()->setQueryChangedInView(true); } void KexiQueryDesignerGuiEditor::slotTableAdded(KexiDB::TableSchema & /*t*/) { if (!d->slotTableAdded_enabled) return; updateColumnsData(); setDirty(); - tempData()->setQueryChangedInPreviousView(true); + tempData()->setQueryChangedInView(true); d->dataTable->setFocus(); } void KexiQueryDesignerGuiEditor::slotTableHidden(KexiDB::TableSchema & /*t*/) { updateColumnsData(); setDirty(); - tempData()->setQueryChangedInPreviousView(true); + tempData()->setQueryChangedInView(true); } QByteArray KexiQueryDesignerGuiEditor::generateUniqueAlias() const { //! @todo add option for using non-i18n'd "expr" prefix? const QByteArray expStr( i18nc("short for 'expression' word (only latin letters, please)", "expr").toLatin1()); //! @todo optimization: cache it? QSet aliases; const int setsSize = d->sets->size(); for (int r = 0; r < setsSize; r++) { //! @todo use iterator here KoProperty::Set *set = d->sets->at(r); if (set) { const QByteArray a((*set)["alias"].value().toByteArray().toLower()); if (!a.isEmpty()) aliases.insert(a); } } int aliasNr = 1; for (;;aliasNr++) { if (!aliases.contains(expStr + QByteArray::number(aliasNr))) break; } return expStr + QByteArray::number(aliasNr); } //! @todo this is primitive, temporary: reuse SQL parser KexiDB::BaseExpr* KexiQueryDesignerGuiEditor::parseExpressionString(const QString& fullString, int& token, bool allowRelationalOperator) { QString str = fullString.trimmed(); int len = 0; //KexiDB::BaseExpr *expr = 0; //1. get token token = 0; //2-char-long tokens if (str.startsWith(QLatin1String(">="))) { token = GREATER_OR_EQUAL; len = 2; } else if (str.startsWith(QLatin1String("<="))) { token = LESS_OR_EQUAL; len = 2; } else if (str.startsWith(QLatin1String("<>"))) { token = NOT_EQUAL; len = 2; } else if (str.startsWith(QLatin1String("!="))) { token = NOT_EQUAL2; len = 2; } else if (str.startsWith(QLatin1String("=="))) { token = '='; len = 2; } else if (str.startsWith(QLatin1String("LIKE "), Qt::CaseInsensitive)) { token = LIKE; len = 5; } else if (str.startsWith(QLatin1String("NOT "), Qt::CaseInsensitive)) { str = str.mid(4).trimmed(); if (str.startsWith(QLatin1String("LIKE "), Qt::CaseInsensitive)) { token = NOT_LIKE; len = 5; } else { return 0; } } else { if (str.startsWith(QLatin1Char('=')) //1-char-long tokens || str.startsWith(QLatin1Char('<')) || str.startsWith(QLatin1Char('>'))) { token = str[0].toLatin1(); len = 1; } else { if (allowRelationalOperator) token = '='; } } if (!allowRelationalOperator && token != 0) return 0; //1. get expression after token if (len > 0) str = str.mid(len).trimmed(); if (str.isEmpty()) return 0; KexiDB::BaseExpr *valueExpr = 0; QRegExp re; if (str.length() >= 2 && ( (str.startsWith(QLatin1Char('"')) && str.endsWith(QLatin1Char('"'))) || (str.startsWith(QLatin1Char('\'')) && str.endsWith(QLatin1Char('\'')))) ) { valueExpr = new KexiDB::ConstExpr(CHARACTER_STRING_LITERAL, str.mid(1, str.length() - 2)); } else if (str.startsWith(QLatin1Char('[')) && str.endsWith(QLatin1Char(']'))) { valueExpr = new KexiDB::QueryParameterExpr(str.mid(1, str.length() - 2)); } else if ((re = QRegExp("(\\d{1,4})-(\\d{1,2})-(\\d{1,2})")).exactMatch(str)) { valueExpr = new KexiDB::ConstExpr(DATE_CONST, QDate::fromString( re.cap(1).rightJustified(4, '0') + "-" + re.cap(2).rightJustified(2, '0') + "-" + re.cap(3).rightJustified(2, '0'), Qt::ISODate)); } else if ((re = QRegExp("(\\d{1,2}):(\\d{1,2})")).exactMatch(str) || (re = QRegExp("(\\d{1,2}):(\\d{1,2}):(\\d{1,2})")).exactMatch(str)) { QString res = re.cap(1).rightJustified(2, '0') + ":" + re.cap(2).rightJustified(2, '0') + ":" + re.cap(3).rightJustified(2, '0'); // kDebug() << res; valueExpr = new KexiDB::ConstExpr(TIME_CONST, QTime::fromString(res, Qt::ISODate)); } else if ((re = QRegExp("(\\d{1,4})-(\\d{1,2})-(\\d{1,2})\\s+(\\d{1,2}):(\\d{1,2})")).exactMatch(str) || (re = QRegExp("(\\d{1,4})-(\\d{1,2})-(\\d{1,2})\\s+(\\d{1,2}):(\\d{1,2}):(\\d{1,2})")).exactMatch(str)) { QString res = re.cap(1).rightJustified(4, '0') + "-" + re.cap(2).rightJustified(2, '0') + "-" + re.cap(3).rightJustified(2, '0') + "T" + re.cap(4).rightJustified(2, '0') + ":" + re.cap(5).rightJustified(2, '0') + ":" + re.cap(6).rightJustified(2, '0'); // kDebug() << res; valueExpr = new KexiDB::ConstExpr(DATETIME_CONST, QDateTime::fromString(res, Qt::ISODate)); } else if ((str[0] >= '0' && str[0] <= '9') || str[0] == '-' || str[0] == '+') { //number QString decimalSym = KGlobal::locale()->decimalSymbol(); bool ok; int pos = str.indexOf('.'); if (pos == -1) {//second chance: local decimal symbol pos = str.indexOf(decimalSym); } if (pos >= 0) {//real const number const int left = str.left(pos).toInt(&ok); if (!ok) return 0; const int right = str.mid(pos + 1).toInt(&ok); if (!ok) return 0; valueExpr = new KexiDB::ConstExpr(REAL_CONST, QPoint(left, right)); //decoded to QPoint } else { //integer const const qint64 val = str.toLongLong(&ok); if (!ok) return 0; valueExpr = new KexiDB::ConstExpr(INTEGER_CONST, val); } } else if (str.toLower() == "null") { valueExpr = new KexiDB::ConstExpr(SQL_NULL, QVariant()); } else {//identfier if (!KexiDB::isIdentifier(str)) return 0; valueExpr = new KexiDB::VariableExpr(str); //find first matching field for name 'str': foreach(KexiRelationsTableContainer *cont, *d->relations->tables()) { /*! @todo what about query? */ if (cont->schema()->table() && cont->schema()->table()->field(str)) { valueExpr->toVariable()->field = cont->schema()->table()->field(str); break; } } } return valueExpr; } void KexiQueryDesignerGuiEditor::slotBeforeCellChanged(KexiDB::RecordData *record, int colnum, QVariant& newValue, KexiDB::ResultInfo* result) { switch (colnum) { case COLUMN_ID_COLUMN: slotBeforeColumnCellChanged(record, newValue, result); break; case COLUMN_ID_TABLE: slotBeforeTableCellChanged(record, newValue, result); break; case COLUMN_ID_VISIBLE: slotBeforeVisibleCellChanged(record, newValue, result); break; #ifndef KEXI_NO_QUERY_TOTALS case COLUMN_ID_TOTALS: slotBeforeTotalsCellChanged(record, newValue, result); break; #endif case COLUMN_ID_SORTING: slotBeforeSortingCellChanged(record, newValue, result); break; case COLUMN_ID_CRITERIA: slotBeforeCriteriaCellChanged(record, newValue, result); break; default: Q_ASSERT_X(false, "colnum", "unhandled value"); } } void KexiQueryDesignerGuiEditor::slotBeforeColumnCellChanged(KexiDB::RecordData *record, QVariant& newValue, KexiDB::ResultInfo* result) { if (newValue.isNull()) { d->data->updateRowEditBuffer(record, COLUMN_ID_TABLE, QVariant(), false/*!allowSignals*/); d->data->updateRowEditBuffer(record, COLUMN_ID_VISIBLE, QVariant(false));//invisible d->data->updateRowEditBuffer(record, COLUMN_ID_SORTING, QVariant()); #ifndef KEXI_NO_QUERY_TOTALS d->data->updateRowEditBuffer(record, COLUMN_ID_TOTALS, QVariant());//remove totals #endif d->data->updateRowEditBuffer(record, COLUMN_ID_CRITERIA, QVariant());//remove crit. d->sets->eraseCurrentPropertySet(); return; } //auto fill 'table' column QString fieldId(newValue.toString().trimmed()); //tmp, can look like "table.field" QString fieldName; //"field" part of "table.field" or expression string QString tableName; //empty for expressions QByteArray alias; QString columnValueForExpr; //for setting pretty printed "alias: expr" in 1st column const bool isExpression = !d->fieldColumnIdentifiers.contains(fieldId.toLower()); if (isExpression) { //this value is entered by hand and doesn't match //any value in the combo box -- we're assuming this is an expression //-table remains null //-find "alias" in something like "alias : expr" const int id = fieldId.indexOf(':'); if (id > 0) { alias = fieldId.left(id).trimmed().toLatin1(); if (!KexiDB::isIdentifier(alias)) { result->success = false; result->allowToDiscardChanges = true; result->column = COLUMN_ID_COLUMN; result->msg = i18n( "Entered column alias \"%1\" is not a valid identifier.", QString(alias)); result->desc = i18n("Identifiers should start with a letter or '_' character"); return; } } fieldName = fieldId.mid(id + 1).trimmed(); //check expr. KexiDB::BaseExpr *e; int dummyToken; if ((e = parseExpressionString(fieldName, dummyToken, false/*allowRelationalOperator*/))) { fieldName = e->toString(0); //print it prettier //this is just checking: destroy expr. object delete e; } else { result->success = false; result->allowToDiscardChanges = true; result->column = COLUMN_ID_COLUMN; result->msg = i18n("Invalid expression \"%1\"", fieldName); return; } } else {//not expr. //this value is properly selected from combo box list if (fieldId == "*") { tableName = "*"; } else { if (!KexiDB::splitToTableAndFieldParts( fieldId, tableName, fieldName, KexiDB::SetFieldNameIfNoTableName)) { kWarning() << "no 'field' or 'table.field'"; return; } } } bool saveOldValue = true; KoProperty::Set *set = d->sets->findPropertySetForItem(*record); if (!set) { saveOldValue = false; // no old val. const int row = d->data->indexOf(record); if (row < 0) { result->success = false; return; } set = createPropertySet(row, tableName, fieldName, true); propertySetSwitched(); } d->data->updateRowEditBuffer(record, COLUMN_ID_TABLE, QVariant(tableName), false/*!allowSignals*/); d->data->updateRowEditBuffer(record, COLUMN_ID_VISIBLE, QVariant(true)); #ifndef KEXI_NO_QUERY_TOTALS d->data->updateRowEditBuffer(record, COLUMN_ID_TOTALS, QVariant(0)); #endif if (!sortingAllowed(fieldName, tableName)) { // sorting is not available for "*" or "table.*" rows //! @todo what about expressions? d->data->updateRowEditBuffer(record, COLUMN_ID_SORTING, QVariant()); } //update properties (*set)["field"].setValue(fieldName, saveOldValue); if (isExpression) { //-no alias but it's needed: if (alias.isEmpty()) //-try oto get old alias alias = (*set)["alias"].value().toByteArray(); if (alias.isEmpty()) //-generate smallest unique alias alias = generateUniqueAlias(); } (*set)["isExpression"].setValue(QVariant(isExpression), saveOldValue); if (!alias.isEmpty()) { (*set)["alias"].setValue(alias, saveOldValue); //pretty printed "alias: expr" newValue = QString(QString(alias) + ": " + fieldName); } (*set)["caption"].setValue(QString(), saveOldValue); (*set)["table"].setValue(tableName, saveOldValue); updatePropertiesVisibility(*set); } void KexiQueryDesignerGuiEditor::slotBeforeTableCellChanged(KexiDB::RecordData *record, QVariant& newValue, KexiDB::ResultInfo* result) { Q_UNUSED(result) if (newValue.isNull()) { if (!(*record)[COLUMN_ID_COLUMN].toString().isEmpty()) { d->data->updateRowEditBuffer(record, COLUMN_ID_COLUMN, QVariant(), false/*!allowSignals*/); } d->data->updateRowEditBuffer(record, COLUMN_ID_VISIBLE, QVariant(false));//invisible #ifndef KEXI_NO_QUERY_TOTALS d->data->updateRowEditBuffer(record, COLUMN_ID_TOTALS, QVariant());//remove totals #endif d->data->updateRowEditBuffer(record, COLUMN_ID_CRITERIA, QVariant());//remove crit. d->sets->eraseCurrentPropertySet(); } //update property KoProperty::Set *set = d->sets->findPropertySetForItem(*record); if (set) { if ((*set)["isExpression"].value().toBool() == false) { (*set)["table"] = newValue; (*set)["caption"] = QVariant(QString()); } else { //do not set table for expr. columns newValue = QVariant(); } updatePropertiesVisibility(*set); } } void KexiQueryDesignerGuiEditor::slotBeforeVisibleCellChanged(KexiDB::RecordData *record, QVariant& newValue, KexiDB::ResultInfo* result) { Q_UNUSED(result) bool saveOldValue = true; if (!propertySet()) { saveOldValue = false; createPropertySet(d->dataTable->dataAwareObject()->currentRow(), (*record)[COLUMN_ID_TABLE].toString(), (*record)[COLUMN_ID_COLUMN].toString(), true); #ifndef KEXI_NO_QUERY_TOTALS d->data->updateRowEditBuffer(record, COLUMN_ID_TOTALS, QVariant(0));//totals #endif propertySetSwitched(); } KoProperty::Set &set = *propertySet(); set["visible"].setValue(newValue, saveOldValue); } void KexiQueryDesignerGuiEditor::slotBeforeTotalsCellChanged(KexiDB::RecordData *record, QVariant& newValue, KexiDB::ResultInfo* result) { #ifdef KEXI_NO_QUERY_TOTALS Q_UNUSED(record) Q_UNUSED(newValue) Q_UNUSED(result) #else //! @todo unused yet setDirty(true); - tempData()->setQueryChangedInPreviousView(true); + tempData()->setQueryChangedInView(true); #endif } void KexiQueryDesignerGuiEditor::slotBeforeSortingCellChanged(KexiDB::RecordData *record, QVariant& newValue, KexiDB::ResultInfo* result) { bool saveOldValue = true; KoProperty::Set *set = d->sets->findPropertySetForItem(*record); if (!set) { saveOldValue = false; set = createPropertySet(d->dataTable->dataAwareObject()->currentRow(), (*record)[COLUMN_ID_TABLE].toString(), (*record)[COLUMN_ID_COLUMN].toString(), true); #ifndef KEXI_NO_QUERY_TOTALS d->data->updateRowEditBuffer(record, COLUMN_ID_TOTALS, QVariant(0));//totals #endif propertySetSwitched(); } QString table(set->property("table").value().toString()); QString field(set->property("field").value().toString()); if (newValue.toInt() == 0 || sortingAllowed(field, table)) { KoProperty::Property &property = set->property("sorting"); QString key(property.listData()->keysAsStringList()[ newValue.toInt()]); kDebug() << "new key=" << key; property.setValue(key, saveOldValue); } else { //show msg: sorting is not available result->success = false; result->allowToDiscardChanges = true; result->column = COLUMN_ID_SORTING; result->msg = i18n("Could not set sorting for multiple columns (%1)", table == "*" ? table : (table + ".*")); } } void KexiQueryDesignerGuiEditor::slotBeforeCriteriaCellChanged(KexiDB::RecordData *record, QVariant& newValue, KexiDB::ResultInfo* result) { //! @todo this is primitive, temporary: reuse SQL parser QString operatorStr, argStr; KexiDB::BaseExpr* e = 0; const QString str = newValue.toString().trimmed(); int token; QString field, table; KoProperty::Set *set = d->sets->findPropertySetForItem(*record); if (set) { field = (*set)["field"].value().toString(); table = (*set)["table"].value().toString(); } if (!str.isEmpty() && (!set || table == "*" || field.contains("*"))) { //asterisk found! criteria not allowed result->success = false; result->allowToDiscardChanges = true; result->column = COLUMN_ID_CRITERIA; if (propertySet()) result->msg = i18n("Could not set criteria for \"%1\"", table == "*" ? table : field); else result->msg = i18n("Could not set criteria for empty record"); } else if (str.isEmpty() || (e = parseExpressionString(str, token, true/*allowRelationalOperator*/))) { if (e) { QString tokenStr; if (token != '=') { KexiDB::BinaryExpr be(KexiDBExpr_Relational, 0, token, 0); tokenStr = be.tokenToString(0) + " "; } if (set) { (*set)["criteria"] = QString(tokenStr + e->toString(0)); //print it prettier } //this is just checking: destroy expr. object delete e; } else if (set && str.isEmpty()) { (*set)["criteria"] = QVariant(); //clear it } setDirty(true); - tempData()->setQueryChangedInPreviousView(true); + tempData()->setQueryChangedInView(true); } else { result->success = false; result->allowToDiscardChanges = true; result->column = COLUMN_ID_CRITERIA; result->msg = i18n("Invalid criteria \"%1\"", newValue.toString()); } } void KexiQueryDesignerGuiEditor::slotTablePositionChanged(KexiRelationsTableContainer*) { setDirty(true); - // this is not needed here because only position has changed: tempData()->setQueryChangedInPreviousView(true); + // this is not needed here because only position has changed: tempData()->setQueryChangedInView(true); } void KexiQueryDesignerGuiEditor::slotAboutConnectionRemove(KexiRelationsConnection*) { setDirty(true); - tempData()->setQueryChangedInPreviousView(true); + tempData()->setQueryChangedInView(true); } void KexiQueryDesignerGuiEditor::slotAppendFields( KexiDB::TableOrQuerySchema& tableOrQuery, const QStringList& fieldNames) { //! @todo how about query columns and multiple fields? KexiDB::TableSchema *table = tableOrQuery.table(); if (!table || fieldNames.isEmpty()) return; QString fieldName(fieldNames.first()); if (fieldName != "*" && !table->field(fieldName)) return; int row_num; //find last filled row in the GUI table for (row_num = d->sets->size() - 1; row_num >= 0 && !d->sets->at(row_num); row_num--) ; row_num++; //after //add row KexiDB::RecordData *newRecord = createNewRow(table->name(), fieldName, true /* visible*/); d->dataTable->dataAwareObject()->insertItem(newRecord, row_num); d->dataTable->dataAwareObject()->setCursorPosition(row_num, 0); //create buffer createPropertySet(row_num, table->name(), fieldName, true/*new one*/); propertySetSwitched(); d->dataTable->setFocus(); } KoProperty::Set *KexiQueryDesignerGuiEditor::propertySet() { return d->sets->currentPropertySet(); } void KexiQueryDesignerGuiEditor::updatePropertiesVisibility(KoProperty::Set& set) { const bool asterisk = isAsterisk( set["table"].value().toString(), set["field"].value().toString() ); #ifdef KEXI_SHOW_UNFINISHED set["caption"].setVisible(!asterisk); #endif set["alias"].setVisible(!asterisk); /*always invisible #ifdef KEXI_SHOW_UNFINISHED set["sorting"].setVisible( !asterisk ); #endif*/ propertySetReloaded(true); } KoProperty::Set* KexiQueryDesignerGuiEditor::createPropertySet(int row, const QString& tableName, const QString& fieldName, bool newOne) { //const bool asterisk = isAsterisk(tableName, fieldName); QString typeName = "KexiQueryDesignerGuiEditor::Column"; KoProperty::Set *set = new KoProperty::Set(d->sets, typeName); KoProperty::Property *prop; //meta-info for property editor set->addProperty(prop = new KoProperty::Property("this:classString", i18n("Query column"))); prop->setVisible(false); //! \todo add table_field icon (add buff->addProperty(prop = new KexiProperty("this:iconName", "table_field") ); // prop->setVisible(false); set->addProperty(prop = new KoProperty::Property("table", QVariant(tableName))); prop->setVisible(false);//always hidden set->addProperty(prop = new KoProperty::Property("field", QVariant(fieldName))); prop->setVisible(false);//always hidden set->addProperty(prop = new KoProperty::Property("caption", QVariant(QString()), i18n("Caption"))); #ifndef KEXI_SHOW_UNFINISHED prop->setVisible(false); #endif set->addProperty(prop = new KoProperty::Property("alias", QVariant(QString()), i18n("Alias"))); set->addProperty(prop = new KoProperty::Property("visible", QVariant(true))); prop->setVisible(false); /*! @todo set->addProperty(prop = new KexiProperty("totals", QVariant(QString())) ); prop->setVisible(false);*/ //sorting QStringList slist, nlist; slist << "nosorting" << "ascending" << "descending"; nlist << i18n("None") << i18n("Ascending") << i18n("Descending"); set->addProperty(prop = new KoProperty::Property("sorting", slist, nlist, slist[0], i18n("Sorting"))); prop->setVisible(false); set->addProperty(prop = new KoProperty::Property("criteria", QVariant(QString()))); prop->setVisible(false); set->addProperty(prop = new KoProperty::Property("isExpression", QVariant(false))); prop->setVisible(false); d->sets->set(row, set, newOne); updatePropertiesVisibility(*set); return set; } void KexiQueryDesignerGuiEditor::setFocus() { d->dataTable->setFocus(); } void KexiQueryDesignerGuiEditor::slotPropertyChanged(KoProperty::Set& set, KoProperty::Property& property) { const QByteArray pname(property.name()); /*! @todo use KexiProperty::setValidator(QString) when implemented as described in TODO #60 */ if (pname == "alias" || pname == "name") { const QVariant& v = property.value(); if (!v.toString().trimmed().isEmpty() && !KexiDB::isIdentifier(v.toString())) { KMessageBox::sorry(this, KexiUtils::identifierExpectedMessage(property.caption(), v.toString())); property.resetValue(); } if (pname == "alias") { if (set["isExpression"].value().toBool() == true) { //update value in column #1 d->dataTable->dataAwareObject()->acceptEditor(); d->data->updateRowEditBuffer(d->dataTable->dataAwareObject()->selectedItem(), 0, QVariant(set["alias"].value().toString() + ": " + set["field"].value().toString())); d->data->saveRowChanges(*d->dataTable->dataAwareObject()->selectedItem(), true); } } } - tempData()->setQueryChangedInPreviousView(true); + tempData()->setQueryChangedInView(true); } void KexiQueryDesignerGuiEditor::slotNewItemStored(KexiPart::Item& item) { d->relations->objectCreated(item.partClass(), item.name()); } void KexiQueryDesignerGuiEditor::slotItemRemoved(const KexiPart::Item& item) { d->relations->objectDeleted(item.partClass(), item.name()); } void KexiQueryDesignerGuiEditor::slotItemRenamed(const KexiPart::Item& item, const QString& oldName) { d->relations->objectRenamed(item.partClass(), oldName, item.name()); } #include "kexiquerydesignerguieditor.moc" diff --git a/kexi/plugins/queries/kexiquerydesignerguieditor.h b/kexi/plugins/queries/kexiquerydesignerguieditor.h index c27bbc68a96..f727219ab96 100644 --- a/kexi/plugins/queries/kexiquerydesignerguieditor.h +++ b/kexi/plugins/queries/kexiquerydesignerguieditor.h @@ -1,182 +1,182 @@ /* This file is part of the KDE project Copyright (C) 2004 Lucijan Busch - Copyright (C) 2004-2015 Jarosław Staniek + Copyright (C) 2004-2016 Jarosław Staniek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KEXIQUERYDESIGNERGUIEDITOR_H #define KEXIQUERYDESIGNERGUIEDITOR_H #include #include "kexiquerypart.h" class QDragMoveEvent; class QDropEvent; class KexiRelationsView; class KexiRelationsTableContainer; class KexiRelationsConnection; namespace KexiPart { class Item; } namespace KoProperty { class Property; class Set; } namespace KexiDB { class Connection; class QuerySchema; class TableSchema; class TableOrQuerySchema; class ResultInfo; class RecordData; } //! Design view of the Query Designer class KexiQueryDesignerGuiEditor : public KexiView { Q_OBJECT public: explicit KexiQueryDesignerGuiEditor(QWidget *parent); virtual ~KexiQueryDesignerGuiEditor(); KexiRelationsView *relationsView() const; virtual QSize sizeHint() const; public Q_SLOTS: virtual void setFocus(); protected: void initTableColumns(); //!< Called just once. void initTableRows(); //!< Called to have all rows empty. virtual tristate beforeSwitchTo(Kexi::ViewMode mode, bool &dontStore); virtual tristate afterSwitchFrom(Kexi::ViewMode mode); virtual KexiDB::SchemaData* storeNewData(const KexiDB::SchemaData& sdata, KexiView::StoreNewDataOptions options, bool &cancel); virtual tristate storeData(bool dontAsk = false); /*! Updates data in columns depending on tables that are currently inserted. Tabular Data in combo box popups is updated as well. */ void updateColumnsData(); /*! \return property buffer associated with currently selected row (i.e. field) or 0 if current row is empty. */ virtual KoProperty::Set *propertySet(); KoProperty::Set* createPropertySet(int row, const QString& tableName, const QString& fieldName, bool newOne = false); /*! Builds query schema out of information provided by gui. The schema is stored in temp->query member. \a errMsg is optional error message returned. \return true on proper schema creation. */ bool buildSchema(QString *errMsg = 0); KexiQueryPart::TempData * tempData() const; /*! Helper: allocates and initializes new table view's row. Doesn't insert it, just returns. \a tableName and \a fieldName should be provided. \a visible flag sets value for "Visible" column. */ KexiDB::RecordData* createNewRow(const QString& tableName, const QString& fieldName, bool visible) const; KexiDB::BaseExpr* parseExpressionString(const QString& fullString, int& token, bool allowRelationalOperator); /*! @internal generates smallest unique alias */ QByteArray generateUniqueAlias() const; void updatePropertiesVisibility(KoProperty::Set& buf); protected Q_SLOTS: void slotDragOverTableRow(KexiDB::RecordData *record, int row, QDragMoveEvent* e); void slotDroppedAtRow(KexiDB::RecordData *record, int row, QDropEvent *ev, KexiDB::RecordData*& newRecord); //! Reaction on appending a new item after deleting one void slotNewItemAppendedForAfterDeletingInSpreadSheetMode(); void slotTableAdded(KexiDB::TableSchema &t); void slotTableHidden(KexiDB::TableSchema &t); //! Called before cell change in tableview. void slotBeforeCellChanged(KexiDB::RecordData* record, int colnum, QVariant& newValue, KexiDB::ResultInfo* result); void slotRowInserted(KexiDB::RecordData* record, uint row, bool repaint); void slotTablePositionChanged(KexiRelationsTableContainer*); void slotAboutConnectionRemove(KexiRelationsConnection*); void slotAppendFields(KexiDB::TableOrQuerySchema& tableOrQuery, const QStringList& fieldNames); /*! Loads layout of relation GUI diagram. */ bool loadLayout(); /*! Stores layout of relation GUI diagram. */ bool storeLayout(); void showTablesForQuery(KexiDB::QuerySchema *query); //! @internal void showFieldsOrRelationsForQueryInternal( KexiDB::QuerySchema *query, bool showFields, bool showRelations, KexiDB::ResultInfo& result); //! convenience method equal to showFieldsOrRelationsForQueryInternal(query, true, true) void showFieldsAndRelationsForQuery(KexiDB::QuerySchema *query, KexiDB::ResultInfo& result); //! convenience method equal to showFieldsOrRelationsForQueryInternal(query, true, false) void showFieldsForQuery(KexiDB::QuerySchema *query, KexiDB::ResultInfo& result); //! convenience method equal to showFieldsOrRelationsForQueryInternal(query, false, true) void showRelationsForQuery(KexiDB::QuerySchema *query, KexiDB::ResultInfo& result); void addConnection(KexiDB::Field *masterField, KexiDB::Field *detailsField); void slotPropertyChanged(KoProperty::Set& set, KoProperty::Property& property); void slotNewItemStored(KexiPart::Item&); void slotItemRemoved(const KexiPart::Item& item); void slotItemRenamed(const KexiPart::Item& item, const QString& oldName); private: void slotBeforeColumnCellChanged(KexiDB::RecordData *record, QVariant& newValue, KexiDB::ResultInfo* result); void slotBeforeTableCellChanged(KexiDB::RecordData *record, QVariant& newValue, KexiDB::ResultInfo* result); void slotBeforeVisibleCellChanged(KexiDB::RecordData *record, QVariant& newValue, KexiDB::ResultInfo* result); void slotBeforeTotalsCellChanged(KexiDB::RecordData *record, QVariant& newValue, KexiDB::ResultInfo* result); void slotBeforeSortingCellChanged(KexiDB::RecordData *record, QVariant& newValue, KexiDB::ResultInfo* result); void slotBeforeCriteriaCellChanged(KexiDB::RecordData *record, QVariant& newValue, KexiDB::ResultInfo* result); class Private; Private * const d; friend class KexiQueryView; // for storeNewData() and storeData() only }; #endif diff --git a/kexi/plugins/queries/kexiquerydesignersql.cpp b/kexi/plugins/queries/kexiquerydesignersql.cpp index ef96356d969..79f0c9bfe2e 100644 --- a/kexi/plugins/queries/kexiquerydesignersql.cpp +++ b/kexi/plugins/queries/kexiquerydesignersql.cpp @@ -1,445 +1,452 @@ /* This file is part of the KDE project Copyright (C) 2003 Lucijan Busch - Copyright (C) 2004-2014 Jarosław Staniek + Copyright (C) 2004-2016 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ +#include "kexiquerydesignersql.h" +#include "kexiquerydesignersqleditor.h" +#include "kexiquerypart.h" +#include "kexisectionheader.h" + #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include -#include "kexiquerydesignersqleditor.h" -#include "kexiquerydesignersql.h" -#include "kexiquerypart.h" - -#include "kexisectionheader.h" - - static bool compareSQL(const QString& sql1, const QString& sql2) { //! @todo use reformatting functions here return sql1.trimmed() == sql2.trimmed(); } //=================== //! @internal class KexiQueryDesignerSQLView::Private { public: Private() : statusPixmapOk(koDesktopIcon("dialog-ok")) , statusPixmapErr(koDesktopIcon("dialog-error")) , statusPixmapInfo(koDesktopIcon("dialog-information")) , parsedQuery(0) , heightForStatusMode(-1) , justSwitchedFromNoViewMode(false) , slotTextChangedEnabled(true) { } KexiQueryDesignerSQLEditor *editor; QLabel *pixmapStatus, *lblStatus; QHBoxLayout *statusHLyr; QFrame *statusMainWidget; KexiSectionHeader *head; QWidget *bottomPane; QPixmap statusPixmapOk, statusPixmapErr, statusPixmapInfo; QSplitter *splitter; //! For internal use, this pointer is usually copied to TempData structure, //! when switching out of this view (then it's cleared). KexiDB::QuerySchema *parsedQuery; //! For internal use, statement passed in switching to this view QString origStatement; //! needed to remember height for both modes, between switching int heightForStatusMode; //! helper for beforeSwitchTo() bool justSwitchedFromNoViewMode; //! helper for slotTextChanged() bool slotTextChangedEnabled; }; //=================== KexiQueryDesignerSQLView::KexiQueryDesignerSQLView(QWidget *parent) : KexiView(parent) , d(new Private()) { d->splitter = new QSplitter(this); d->splitter->setOrientation(Qt::Vertical); d->head = new KexiSectionHeader(i18n("SQL Query Text"), Qt::Vertical, d->splitter); d->splitter->addWidget(d->head); d->splitter->setStretchFactor( d->splitter->indexOf(d->head), 3/*stretch*/); d->editor = new KexiQueryDesignerSQLEditor(d->head); d->editor->setObjectName("sqleditor"); d->head->setWidget(d->editor); connect(d->editor, SIGNAL(textChanged()), this, SLOT(slotTextChanged())); // -- bottom pane (status) d->bottomPane = new QWidget(d->splitter); QVBoxLayout *bottomPaneLyr = new QVBoxLayout(d->bottomPane); d->splitter->addWidget(d->bottomPane); d->splitter->setStretchFactor( d->splitter->indexOf(d->bottomPane), 1/*KeepSize*/); // -- status pane d->statusMainWidget = new QFrame(d->bottomPane); bottomPaneLyr->addWidget(d->statusMainWidget); d->statusMainWidget->setAutoFillBackground(true); d->statusMainWidget->setFrameShape(QFrame::StyledPanel); d->statusMainWidget->setFrameShadow(QFrame::Plain); d->statusMainWidget->setBackgroundRole(QPalette::Base); QPalette pal(QToolTip::palette()); pal.setBrush(QPalette::Base, QToolTip::palette().brush(QPalette::Button)); d->statusMainWidget->setPalette(pal); d->splitter->setCollapsible(1, false); d->statusHLyr = new QHBoxLayout(d->statusMainWidget); d->statusHLyr->setContentsMargins(0, KDialog::marginHint() / 2, 0, KDialog::marginHint() / 2); d->statusHLyr->setSpacing(0); d->pixmapStatus = new QLabel(d->statusMainWidget); d->statusHLyr->addWidget(d->pixmapStatus); d->pixmapStatus->setFixedWidth(d->statusPixmapOk.width()*3 / 2); d->pixmapStatus->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter); d->pixmapStatus->setAutoFillBackground(true); d->lblStatus = new QLabel(d->statusMainWidget); d->statusHLyr->addWidget(d->lblStatus); d->lblStatus->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); d->lblStatus->setWordWrap(true); d->lblStatus->setTextInteractionFlags(Qt::TextBrowserInteraction); d->lblStatus->setMinimumHeight(d->statusPixmapOk.width()); addChildView(d->editor); setViewWidget(d->splitter, false/* no focus proxy*/); d->splitter->setFocusProxy(d->editor); setFocusProxy(d->editor); // -- setup local actions QList viewActions; QAction* a; viewActions << (a = new KAction(koIcon("test_it"), i18n("Check Query"), this)); a->setObjectName("querypart_check_query"); a->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_F5)); a->setToolTip(i18n("Check Query")); a->setWhatsThis(i18n("Checks query for validity.")); addAction(a); connect(a, SIGNAL(triggered()), this, SLOT(slotCheckQuery())); setViewActions(viewActions); slotUpdateMode(); slotCheckQuery(); updateGeometry(); } KexiQueryDesignerSQLView::~KexiQueryDesignerSQLView() { delete d; } KexiQueryDesignerSQLEditor *KexiQueryDesignerSQLView::editor() const { return d->editor; } void KexiQueryDesignerSQLView::setStatusOk() { d->pixmapStatus->setPixmap(d->statusPixmapOk); setStatusText("

" + i18n("The query is correct") + "

"); } void KexiQueryDesignerSQLView::setStatusError(const QString& msg) { d->pixmapStatus->setPixmap(d->statusPixmapErr); setStatusText("

" + i18n("The query is incorrect") + "

" + msg + "

"); } void KexiQueryDesignerSQLView::setStatusEmpty() { d->pixmapStatus->setPixmap(d->statusPixmapInfo); setStatusText( i18n("Please enter your query and execute \"Check query\" function to verify it.")); } void KexiQueryDesignerSQLView::setStatusText(const QString& text) { d->lblStatus->setText(text); } tristate KexiQueryDesignerSQLView::beforeSwitchTo(Kexi::ViewMode mode, bool &dontStore) { //! @todo dontStore = true; if (mode == Kexi::DesignViewMode || mode == Kexi::DataViewMode) { QString sqlText = d->editor->text().trimmed(); KexiQueryPart::TempData * temp = tempData(); const bool sqlTextIsEmpty = sqlText.isEmpty(); if (sqlTextIsEmpty && mode == Kexi::DesignViewMode) { //special case: empty SQL text, allow to switch to the design view if (temp->query()) { - temp->setQueryChangedInPreviousView(true); //query changed + temp->setQueryChangedInView(true); //query changed temp->setQuery(0); } } else { const bool designViewWasVisible = window()->viewForMode(mode) != 0; //should we check SQL text? if (designViewWasVisible && !sqlTextIsEmpty //for empty text always show error && !d->justSwitchedFromNoViewMode //unchanged, but we should check SQL text && compareSQL(d->origStatement, d->editor->text())) { //statement unchanged! - nothing to do - temp->setQueryChangedInPreviousView(false); + temp->setQueryChangedInView(false); } else { //yes: parse SQL text if (sqlTextIsEmpty || !slotCheckQuery()) { if (KMessageBox::No == KMessageBox::warningYesNo(this, "

" + i18n("The query you entered is incorrect.") + "

" + i18n("Do you want to cancel any changes made to this SQL text?") + "

" + "

" + i18n("Answering \"No\" allows you to make corrections.") + "

")) { return cancelled; } //do not change original query - it's invalid - temp->setQueryChangedInPreviousView(false); + temp->setQueryChangedInView(false); //this view is no longer _just_ switched from "NoViewMode" d->justSwitchedFromNoViewMode = false; return true; } //this view is no longer _just_ switched from "NoViewMode" d->justSwitchedFromNoViewMode = false; //replace old query schema with new one temp->setQuery(d->parsedQuery); //this will also delete temp->query() d->parsedQuery = 0; - temp->setQueryChangedInPreviousView(true); + temp->setQueryChangedInView(true); } } d->origStatement = d->editor->text(); } d->editor->setFocus(); return true; } tristate KexiQueryDesignerSQLView::afterSwitchFrom(Kexi::ViewMode mode) { kDebug(); if (mode == Kexi::NoViewMode) { //User opened text view _directly_. //This flag is set to indicate for beforeSwitchTo() that even if text has not been changed, //SQL text should be invalidated. d->justSwitchedFromNoViewMode = true; } KexiQueryPart::TempData * temp = tempData(); KexiDB::QuerySchema *query = temp->query(); if (!query) {//try to just get saved schema, instead of temporary one query = dynamic_cast(window()->schemaData()); } if (mode != 0/*failure only if it is switching from prev. view*/ && !query) { //! @todo msg return false; } if (query) { // Use query with Kexi keywords (but not driver-specific keywords) escaped. temp->setQuery(query); - if (temp->queryChangedInPreviousView()) { + if (temp->queryChangedInView() != Kexi::NoViewMode) { KexiDB::Connection::SelectStatementOptions options; options.identifierEscaping = KexiDB::Driver::EscapeKexi; options.addVisibleLookupColumns = false; d->origStatement = KexiDB::selectStatement(0, *query, options).trimmed(); } } if (d->origStatement.isEmpty() && !window()->partItem()->neverSaved()) { //no valid query delivered or query has not been modified: // just load sql text, no matter if it's valid if (!loadDataBlock(d->origStatement, "sql", true /*canBeEmpty*/)) return false; } - if (!compareSQL(d->origStatement, d->editor->text())) { - d->slotTextChangedEnabled = false; - d->editor->setText(d->origStatement); - d->slotTextChangedEnabled = true; + if (temp->queryChangedInView() == Kexi::DesignViewMode /* true in this scenario: + - user switched from SQL to Design, + - changed the design, + - switched to Data + - switched back to SQL */ + || mode != Kexi::DataViewMode) /* true in this scenario: user switched from No-view + or Design view */ + { + if (!compareSQL(d->origStatement, d->editor->text())) { + d->slotTextChangedEnabled = false; + d->editor->setText(d->origStatement); + d->slotTextChangedEnabled = true; + } } QTimer::singleShot(100, d->editor, SLOT(setFocus())); return true; } QString KexiQueryDesignerSQLView::sqlText() const { return d->editor->text(); } bool KexiQueryDesignerSQLView::slotCheckQuery() { QString sqlText(d->editor->text().trimmed()); if (sqlText.isEmpty()) { delete d->parsedQuery; d->parsedQuery = 0; setStatusEmpty(); return true; } kDebug(); KexiDB::Parser *parser = KexiMainWindowIface::global()->project()->sqlParser(); const bool ok = parser->parse(sqlText); delete d->parsedQuery; d->parsedQuery = parser->query(); if (!d->parsedQuery || !ok || !parser->error().type().isEmpty()) { KexiDB::ParserError err = parser->error(); setStatusError(err.error()); d->editor->jump(err.at()); delete d->parsedQuery; d->parsedQuery = 0; return false; } setStatusOk(); return true; } void KexiQueryDesignerSQLView::slotUpdateMode() { slotCheckQuery(); } void KexiQueryDesignerSQLView::slotTextChanged() { if (!d->slotTextChangedEnabled) return; setDirty(true); setStatusEmpty(); } void KexiQueryDesignerSQLView::updateActions(bool activated) { if (activated) { slotUpdateMode(); } setAvailable("querypart_check_query", true); KexiView::updateActions(activated); } KexiQueryPart::TempData* KexiQueryDesignerSQLView::tempData() const { return dynamic_cast(window()->data()); } KexiDB::SchemaData* KexiQueryDesignerSQLView::storeNewData(const KexiDB::SchemaData& sdata, KexiView::StoreNewDataOptions options, bool &cancel) { Q_UNUSED(options); //here: we won't store query layout: it will be recreated 'by hand' in GUI Query Editor const bool queryOK = slotCheckQuery(); bool ok = true; KexiDB::SchemaData* query = 0; if (queryOK) { if (d->parsedQuery) { query = d->parsedQuery; //will be returned, so: don't keep it d->parsedQuery = 0; } else { //empty query query = new KexiDB::SchemaData(); //just empty } } else { //the query is not ok if (KMessageBox::Yes != KMessageBox::questionYesNo(this, i18n("Do you want to save invalid query?"), 0, KStandardGuiItem::yes(), KStandardGuiItem::no(), "askBeforeSavingInvalidQueries"/*config entry*/)) { cancel = true; return 0; } query = new KexiDB::SchemaData(); //just empty } (KexiDB::SchemaData&)*query = sdata; //copy main attributes ok = KexiMainWindowIface::global()->project()->dbConnection()->storeObjectSchemaData( *query, true /*newObject*/); if (ok) { ok = KexiMainWindowIface::global()->project()->removeUserDataBlock(query->id()); // for sanity } if (ok) { window()->setId(query->id()); ok = storeDataBlock(d->editor->text(), "sql"); } if (!ok) { delete query; query = 0; } return query; } tristate KexiQueryDesignerSQLView::storeData(bool dontAsk) { if (window()->schemaData()) { //set this instance as obsolete (only if it's stored) KexiMainWindowIface::global()->project()->dbConnection()->setQuerySchemaObsolete(window()->schemaData()->name()); } tristate res = KexiView::storeData(dontAsk); if (~res) return res; if (res == true) { res = storeDataBlock(d->editor->text(), "sql"); #if 0 bool queryOK = slotCheckQuery(); if (queryOK) { res = storeDataBlock(d->editor->text(), "sql"); } else { //query is not ok //! @todo allow saving invalid queries //! @todo just ask this question: res = false; } #endif } if (res == true) { QString empty_xml; res = storeDataBlock(empty_xml, "query_layout"); //clear } if (!res) setDirty(true); return res; } #include "kexiquerydesignersql.moc" diff --git a/kexi/plugins/queries/kexiquerydesignersql.h b/kexi/plugins/queries/kexiquerydesignersql.h index 8f7b00d25d6..1726c2ef9e9 100644 --- a/kexi/plugins/queries/kexiquerydesignersql.h +++ b/kexi/plugins/queries/kexiquerydesignersql.h @@ -1,79 +1,79 @@ /* This file is part of the KDE project Copyright (C) 2003 Lucijan Busch - Copyright (C) 2004-2012 Jarosław Staniek + Copyright (C) 2004-2016 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KEXIQUERYDESIGNERSQL_H #define KEXIQUERYDESIGNERSQL_H #include "kexiquerypart.h" #include #include class KexiQueryDesignerSQLEditor; //! The KexiQueryDesignerSQLView class for editing Queries in text mode. /*! It is a view containing SQL text editor and SQL status widget split vertically. */ class KexiQueryDesignerSQLView : public KexiView { Q_OBJECT public: explicit KexiQueryDesignerSQLView(QWidget *parent); virtual ~KexiQueryDesignerSQLView(); QString sqlText() const; KexiQueryDesignerSQLEditor *editor() const; protected: KexiQueryPart::TempData * tempData() const; virtual tristate beforeSwitchTo(Kexi::ViewMode mode, bool &dontStore); virtual tristate afterSwitchFrom(Kexi::ViewMode mode); virtual KexiDB::SchemaData* storeNewData(const KexiDB::SchemaData& sdata, KexiView::StoreNewDataOptions options, bool &cancel); virtual tristate storeData(bool dontAsk = false); void setStatusOk(); void setStatusError(const QString& msg); void setStatusEmpty(); void setStatusText(const QString& text); virtual void updateActions(bool activated); protected Q_SLOTS: /*! Performs query checking (by text parsing). \return true and sets d->parsedQuery to the new query schema object on success. */ bool slotCheckQuery(); void slotUpdateMode(); void slotTextChanged(); Q_SIGNALS: void queryShortcut(); private: class Private; Private * const d; friend class KexiQueryView; // for storeNewData() and storeData() only }; #endif diff --git a/kexi/plugins/queries/kexiquerypart.cpp b/kexi/plugins/queries/kexiquerypart.cpp index ce7548e1c9e..24d43fd1cef 100644 --- a/kexi/plugins/queries/kexiquerypart.cpp +++ b/kexi/plugins/queries/kexiquerypart.cpp @@ -1,253 +1,254 @@ /* This file is part of the KDE project Copyright (C) 2004 Lucijan Busch - Copyright (C) 2004-2010 Jarosław Staniek + Copyright (C) 2004-2016 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kexiquerypart.h" #include #include #include #include #include #include #include #include "kexiqueryview.h" #include "kexiquerydesignerguieditor.h" #include "kexiquerydesignersql.h" //------------------------------------------------ KexiQueryPart::KexiQueryPart(QObject *parent, const QVariantList &l) : KexiPart::Part(parent, i18nc("Translate this word using only lowercase alphanumeric characters (a..z, 0..9). " "Use '_' character instead of spaces. First character should be a..z character. " "If you cannot use latin characters in your language, use english word.", "query"), i18nc("tooltip", "Create new query"), i18nc("what's this", "Creates new query."), l) { setInternalPropertyValue("textViewModeCaption", i18n("SQL")); } KexiQueryPart::~KexiQueryPart() { } KexiWindowData* KexiQueryPart::createWindowData(KexiWindow* window) { KexiQueryPart::TempData *data = new KexiQueryPart::TempData( window, KexiMainWindowIface::global()->project()->dbConnection()); data->listenerInfoString = i18nc("@info Object \"objectname\"", "%1 %2", window->part()->info()->instanceCaption(), window->partItem()->name()); return data; } KexiView* KexiQueryPart::createView(QWidget *parent, KexiWindow* window, KexiPart::Item &item, Kexi::ViewMode viewMode, QMap*) { Q_UNUSED(item); Q_UNUSED(window); //kDebug(); KexiView* view = 0; if (viewMode == Kexi::DataViewMode) { view = new KexiQueryView(parent); view->setObjectName("dataview"); } else if (viewMode == Kexi::DesignViewMode) { view = new KexiQueryDesignerGuiEditor(parent); view->setObjectName("guieditor"); //needed for updating tables combo box: KexiProject *prj = KexiMainWindowIface::global()->project(); connect(prj, SIGNAL(newItemStored(KexiPart::Item&)), view, SLOT(slotNewItemStored(KexiPart::Item&))); connect(prj, SIGNAL(itemRemoved(KexiPart::Item)), view, SLOT(slotItemRemoved(KexiPart::Item))); connect(prj, SIGNAL(itemRenamed(KexiPart::Item,QString)), view, SLOT(slotItemRenamed(KexiPart::Item,QString))); } else if (viewMode == Kexi::TextViewMode) { view = new KexiQueryDesignerSQLView(parent); view->setObjectName("sqldesigner"); } return view; } tristate KexiQueryPart::remove(KexiPart::Item &item) { if (!KexiMainWindowIface::global()->project() || !KexiMainWindowIface::global()->project()->dbConnection()) return false; KexiDB::Connection *conn = KexiMainWindowIface::global()->project()->dbConnection(); KexiDB::QuerySchema *sch = conn->querySchema(item.identifier()); if (sch) return conn->dropQuery(sch); //last chance: just remove item return conn->removeObject(item.identifier()); } void KexiQueryPart::initPartActions() { } void KexiQueryPart::initInstanceActions() { } KexiDB::SchemaData* KexiQueryPart::loadSchemaData( KexiWindow *window, const KexiDB::SchemaData& sdata, Kexi::ViewMode viewMode, bool *ownedByWindow) { KexiQueryPart::TempData * temp = static_cast(window->data()); QString sqlText; if (!loadDataBlock(window, sqlText, "sql")) { return 0; } KexiDB::Parser *parser = KexiMainWindowIface::global()->project()->sqlParser(); parser->parse(sqlText); KexiDB::QuerySchema *query = parser->query(); //error? if (!query) { if (viewMode == Kexi::TextViewMode) { //for SQL view, no parsing is initially needed: //-just make a copy: return KexiPart::Part::loadSchemaData(window, sdata, viewMode, ownedByWindow); } /* Set this to true on data loading loadSchemaData() to indicate that TextView mode could be used instead of DataView or DesignView, because there are problems with opening object. */ temp->proposeOpeningInTextViewModeBecauseOfProblems = true; //! @todo return 0; } query->debug(); (KexiDB::SchemaData&)*query = sdata; //copy main attributes temp->registerTableSchemaChanges(query); if (ownedByWindow) *ownedByWindow = false; query->debug(); return query; } KLocalizedString KexiQueryPart::i18nMessage(const QString& englishMessage, KexiWindow* window) const { if (englishMessage == "Design of object %1 has been modified.") return ki18n(I18N_NOOP("Design of query %1 has been modified.")); if (englishMessage == "Object %1 already exists.") return ki18n(I18N_NOOP("Query %1 already exists.")); return Part::i18nMessage(englishMessage, window); } tristate KexiQueryPart::rename(KexiPart::Item &item, const QString& newName) { Q_UNUSED(newName); if (!KexiMainWindowIface::global()->project()->dbConnection()) return false; KexiMainWindowIface::global()->project()->dbConnection() ->setQuerySchemaObsolete(item.name()); return true; } //---------------- KexiQueryPart::TempData::TempData(KexiWindow* window, KexiDB::Connection *conn) : KexiWindowData(window) , KexiDB::Connection::TableSchemaChangeListenerInterface() , m_query(0) - , m_queryChangedInPreviousView(false) + , m_queryChangedInView(Kexi::NoViewMode) { this->conn = conn; } KexiQueryPart::TempData::~TempData() { conn->unregisterForTablesSchemaChanges(*this); } void KexiQueryPart::TempData::clearQuery() { if (!m_query) return; unregisterForTablesSchemaChanges(); m_query->clear(); } void KexiQueryPart::TempData::unregisterForTablesSchemaChanges() { conn->unregisterForTablesSchemaChanges(*this); } void KexiQueryPart::TempData::registerTableSchemaChanges(KexiDB::QuerySchema *q) { if (!q) return; foreach(KexiDB::TableSchema* table, *q->tables()) { conn->registerForTableSchemaChanges(*this, *table); } } tristate KexiQueryPart::TempData::closeListener() { KexiWindow* window = static_cast(parent()); return KexiMainWindowIface::global()->closeWindow(window); } KexiDB::QuerySchema *KexiQueryPart::TempData::takeQuery() { KexiDB::QuerySchema *query = m_query; m_query = 0; return query; } void KexiQueryPart::TempData::setQuery(KexiDB::QuerySchema *query) { if (m_query && m_query == query) return; if (m_query /* query not owned by window */ && (static_cast(parent())->schemaData() != static_cast(m_query))) { delete m_query; } m_query = query; } -bool KexiQueryPart::TempData::queryChangedInPreviousView() const +Kexi::ViewMode KexiQueryPart::TempData::queryChangedInView() const { - return m_queryChangedInPreviousView; + return m_queryChangedInView; } -void KexiQueryPart::TempData::setQueryChangedInPreviousView(bool set) +void KexiQueryPart::TempData::setQueryChangedInView(bool set) { - m_queryChangedInPreviousView = set; + m_queryChangedInView = set ? qobject_cast(parent())->currentViewMode() + : Kexi::NoViewMode; } //---------------- K_EXPORT_KEXIPART_PLUGIN( KexiQueryPart, query ) #include "kexiquerypart.moc" diff --git a/kexi/plugins/queries/kexiquerypart.h b/kexi/plugins/queries/kexiquerypart.h index 09491f56392..a80c1169cf8 100644 --- a/kexi/plugins/queries/kexiquerypart.h +++ b/kexi/plugins/queries/kexiquerypart.h @@ -1,119 +1,122 @@ /* This file is part of the KDE project Copyright (C) 2003 Lucijan Busch - Copyright (C) 2004-2010 Jarosław Staniek + Copyright (C) 2004-2016 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KEXIQUERYPART_H #define KEXIQUERYPART_H #include #include #include #include #include #include namespace KexiDB { class QuerySchema; class Connection; } //! @short Kexi Query Designer Plugin. class KexiQueryPart : public KexiPart::Part { Q_OBJECT public: KexiQueryPart(QObject *parent, const QVariantList &); virtual ~KexiQueryPart(); virtual tristate remove(KexiPart::Item &item); //! @short Temporary data kept in memory while switching between Query Window's views class TempData : public KexiWindowData, public KexiDB::Connection::TableSchemaChangeListenerInterface { public: TempData(KexiWindow* parent, KexiDB::Connection *conn); virtual ~TempData(); virtual tristate closeListener(); void clearQuery(); void unregisterForTablesSchemaChanges(); void registerTableSchemaChanges(KexiDB::QuerySchema *q); /*! Assigns query \a query for this data. Existing query (available using query()) is deleted but only if it is not owned by parent window (i.e. != KexiWindow::schemaData()). \a query can be 0. If \a query is equal to existing query, nothing is performed. */ void setQuery(KexiDB::QuerySchema *query); //! \return query associated with this data KexiDB::QuerySchema *query() const { return m_query; } //! Takes query associated with this data (without deleting) and returns it. //! After this call query() == 0 KexiDB::QuerySchema *takeQuery(); //! Connection used for retrieving definition of the query KexiDB::Connection *conn; - /*! @return true if \a query member has changed in previous view. + /*! @return view mode if which the query member has changed. + It's possibly one of previously visited views. Kexi::NoViewMode is the default, + what means that query was not changed. Used on view switching. We're checking this flag to see if we should rebuild internal structure for DesignViewMode of regenerated sql text in TextViewMode after switch from other view. */ - bool queryChangedInPreviousView() const; + Kexi::ViewMode queryChangedInView() const; - /*! Sets the queryChangedInPreviousView flag. - @see queryChangedInPreviousView() */ - void setQueryChangedInPreviousView(bool set); + /*! Sets the queryChangedInView flag. If @a set is true, then the flag is changed + to the current view mode. If @a set is false, the flag is changed to Kexi::NoViewMode. + @see queryChangedInView() */ + void setQueryChangedInView(bool set); private: KexiDB::QuerySchema *m_query; - bool m_queryChangedInPreviousView; + Kexi::ViewMode m_queryChangedInView; }; virtual KLocalizedString i18nMessage(const QString& englishMessage, KexiWindow* window) const; /*! Renames stored data pointed by \a item to \a newName. Reimplemented to mark the query obsolete by using KexiDB::Connection::setQuerySchemaObsolete(). */ virtual tristate rename(KexiPart::Item & item, const QString& newName); protected: virtual KexiWindowData* createWindowData(KexiWindow* window); virtual KexiView* createView(QWidget *parent, KexiWindow* window, KexiPart::Item &item, Kexi::ViewMode viewMode = Kexi::DataViewMode, QMap* staticObjectArgs = 0); virtual void initPartActions(); virtual void initInstanceActions(); virtual KexiDB::SchemaData* loadSchemaData(KexiWindow *window, const KexiDB::SchemaData& sdata, Kexi::ViewMode viewMode, bool *ownedByWindow); }; #endif