/* This file is part of the KDE project Copyright (C) 2004 Lucijan Busch Copyright (C) 2004-2006 Jaroslaw 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 "kexiquerypart.h" //! @todo remove KEXI_NO_TQUERY_TOTALS later #define KEXI_NO_TQUERY_TOTALS //! indices for table columns #define COLUMN_ID_COLUMN 0 #define COLUMN_ID_TABLE 1 #define COLUMN_ID_VISIBLE 2 #ifdef KEXI_NO_TQUERY_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() : fieldColumnIdentifiers(101, false/*case insens.*/) { droppedNewItem = 0; slotTableAdded_enabled = true; } bool changeSingleCellValue(KexiTableItem &item, int columnNumber, const TQVariant& value, KexiDB::ResultInfo* result) { data->clearRowEditBuffer(); if (!data->updateRowEditBuffer(&item, columnNumber, value) || !data->saveRowChanges(item, true)) { if (result) *result = *data->result(); return false; } return true; } KexiTableViewData *data; KexiDataTable *dataTable; TQGuardedPtr conn; KexiRelationWidget *relations; KexiSectionHeader *head; TQSplitter *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(). */ KexiTableViewData *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); */ TQDict fieldColumnIdentifiers; KexiDataAwarePropertySet* sets; KexiTableItem *droppedNewItem; TQString droppedNewTable, droppedNewField; bool slotTableAdded_enabled : 1; }; static bool isAsterisk(const TQString& tableName, const TQString& fieldName) { return tableName=="*" || fieldName.endsWith("*"); } //! @internal \return true if sorting is allowed for \a fieldName and \a tableName static bool sortingAllowed(const TQString& fieldName, const TQString& tableName) { return ! (fieldName=="*" || (fieldName.isEmpty() && tableName=="*")); } //========================================================= KexiQueryDesignerGuiEditor::KexiQueryDesignerGuiEditor( KexiMainWindow *mainWin, TQWidget *tqparent, const char *name) : KexiViewBase(mainWin, tqparent, name) , d( new Private() ) { d->conn = mainWin->project()->dbConnection(); d->spl = new TQSplitter(Qt::Vertical, this); d->spl->setChildrenCollapsible(false); d->relations = new KexiRelationWidget(mainWin, d->spl, "relations"); connect(d->relations, TQT_SIGNAL(tableAdded(KexiDB::TableSchema&)), this, TQT_SLOT(slotTableAdded(KexiDB::TableSchema&))); connect(d->relations, TQT_SIGNAL(tableHidden(KexiDB::TableSchema&)), this, TQT_SLOT(slotTableHidden(KexiDB::TableSchema&))); connect(d->relations, TQT_SIGNAL(tableFieldDoubleClicked(KexiDB::TableSchema*,const TQString&)), this, TQT_SLOT(slotTableFieldDoubleClicked(KexiDB::TableSchema*,const TQString&))); d->head = new KexiSectionHeader(i18n("Query Columns"),Qt::Vertical, d->spl); d->dataTable = new KexiDataTable(mainWin, d->head, "guieditor_dataTable", false); d->dataTable->dataAwareObject()->setSpreadSheetMode(); d->data = new KexiTableViewData(); //just empty data d->sets = new KexiDataAwarePropertySet( this, d->dataTable->dataAwareObject() ); initTableColumns(); initTableRows(); TQValueList 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()->adjustColumnWidthToContents(COLUMN_ID_SORTING); d->dataTable->tableView()->maximizeColumnsWidth( c ); d->dataTable->tableView()->setDropsAtRowEnabled(true); connect(d->dataTable->tableView(), TQT_SIGNAL(dragOverRow(KexiTableItem*,int,TQDragMoveEvent*)), this, TQT_SLOT(slotDragOverTableRow(KexiTableItem*,int,TQDragMoveEvent*))); connect(d->dataTable->tableView(), TQT_SIGNAL(droppedAtRow(KexiTableItem*,int,TQDropEvent*,KexiTableItem*&)), this, TQT_SLOT(slotDroppedAtRow(KexiTableItem*,int,TQDropEvent*,KexiTableItem*&))); connect(d->dataTable->tableView(), TQT_SIGNAL(newItemAppendedForAfterDeletingInSpreadSheetMode()), this, TQT_SLOT(slotNewItemAppendedForAfterDeletingInSpreadSheetMode())); } connect(d->data, TQT_SIGNAL(aboutToChangeCell(KexiTableItem*,int,TQVariant&,KexiDB::ResultInfo*)), this, TQT_SLOT(slotBeforeCellChanged(KexiTableItem*,int,TQVariant&,KexiDB::ResultInfo*))); connect(d->data, TQT_SIGNAL(rowInserted(KexiTableItem*,uint,bool)), this, TQT_SLOT(slotRowInserted(KexiTableItem*,uint,bool))); connect(d->relations, TQT_SIGNAL(tablePositionChanged(KexiRelationViewTableContainer*)), this, TQT_SLOT(slotTablePositionChanged(KexiRelationViewTableContainer*))); connect(d->relations, TQT_SIGNAL(aboutConnectionRemove(KexiRelationViewConnection*)), this, TQT_SLOT(slotAboutConnectionRemove(KexiRelationViewConnection*))); TQVBoxLayout *l = new TQVBoxLayout(this); l->addWidget(d->spl); addChildView(d->relations); addChildView(d->dataTable); setViewWidget(d->dataTable, true); d->relations->tqsetSizePolicy(TQSizePolicy::Expanding,TQSizePolicy::Expanding); d->head->tqsetSizePolicy(TQSizePolicy::Expanding,TQSizePolicy::Expanding); updateGeometry(); d->spl->setSizes(TQValueList()<< 800<<400); } KexiQueryDesignerGuiEditor::~KexiQueryDesignerGuiEditor() { } void KexiQueryDesignerGuiEditor::initTableColumns() { KexiTableViewColumn *col1 = new KexiTableViewColumn("column", KexiDB::Field::Enum, i18n("Column"), i18n("Describes field name or expression for the designed query.")); col1->setRelatedDataEditable(true); d->fieldColumnData = new KexiTableViewData(KexiDB::Field::Text, KexiDB::Field::Text); col1->setRelatedData( d->fieldColumnData ); d->data->addColumn(col1); KexiTableViewColumn *col2 = new KexiTableViewColumn("table", KexiDB::Field::Enum, i18n("Table"), i18n("Describes table for a given field. Can be empty.")); d->tablesColumnData = new KexiTableViewData(KexiDB::Field::Text, KexiDB::Field::Text); col2->setRelatedData( d->tablesColumnData ); d->data->addColumn(col2); KexiTableViewColumn *col3 = new KexiTableViewColumn("visible", KexiDB::Field::Boolean, i18n("Visible"), i18n("Describes visibility for a given field or expression.")); col3->field()->setDefaultValue( TQVariant(false, 0) ); col3->field()->setNotNull( true ); d->data->addColumn(col3); #ifndef KEXI_NO_TQUERY_TOTALS KexiTableViewColumn *col4 = new KexiTableViewColumn("totals", KexiDB::Field::Enum, i18n("Totals"), i18n("Describes a way of computing totals for a given field or expression.")); TQValueVector totalsTypes; totalsTypes.append( i18n("Group by") ); totalsTypes.append( i18n("Sum") ); totalsTypes.append( i18n("Average") ); totalsTypes.append( i18n("Min") ); totalsTypes.append( i18n("Max") ); //todo: more like this col4->field()->setEnumHints(totalsTypes); d->data->addColumn(col4); #endif KexiTableViewColumn *col5 = new KexiTableViewColumn("sort", KexiDB::Field::Enum, i18n("Sorting"), i18n("Describes a way of sorting for a given field.")); TQValueVector sortTypes; sortTypes.append( "" ); sortTypes.append( i18n("Ascending") ); sortTypes.append( i18n("Descending") ); col5->field()->setEnumHints(sortTypes); d->data->addColumn(col5); KexiTableViewColumn *col6 = new KexiTableViewColumn("criteria", KexiDB::Field::Text, i18n("Criteria"), i18n("Describes the criteria for a given field or expression.")); d->data->addColumn(col6); // KexiTableViewColumn *col7 = new KexiTableViewColumn(i18n("Or"), KexiDB::Field::Text); // d->data->addColumn(col7); } void KexiQueryDesignerGuiEditor::initTableRows() { d->data->deleteAllRows(); //const int columns = d->data->columnsCount(); for (int i=0; i<(int)d->sets->size(); i++) { KexiTableItem* item; d->data->append(item = d->data->createItem()); item->at(COLUMN_ID_VISIBLE) = TQVariant(false, 0); } d->dataTable->dataAwareObject()->setData(d->data); updateColumnsData(); } void KexiQueryDesignerGuiEditor::updateColumnsData() { d->dataTable->dataAwareObject()->acceptRowEdit(); TQStringList sortedTableNames; for (TablesDictIterator it(*d->relations->tables());it.current();++it) sortedTableNames += it.current()->schema()->name(); qHeapSort( sortedTableNames ); //several tables can be hidden now, so remove rows for these tables TQValueList rowsToDelete; for (int r = 0; r<(int)d->sets->size(); r++) { KoProperty::Set *set = d->sets->at(r); if (set) { TQString tableName = (*set)["table"].value().toString(); TQString fieldName = (*set)["field"].value().toString(); const bool allTablesAsterisk = tableName=="*" && d->relations->tables()->isEmpty(); const bool fieldNotFound = tableName!="*" && !(*set)["isExpression"].value().toBool() && sortedTableNames.end() == tqFind( 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(); KexiTableItem *item = d->fieldColumnData->createItem(); //new KexiTableItem(2); (*item)[COLUMN_ID_COLUMN]="*"; (*item)[COLUMN_ID_TABLE]="*"; d->fieldColumnData->append( item ); d->fieldColumnIdentifiers.insert((*item)[COLUMN_ID_COLUMN].toString(), (char*)1); //cache // tempData()->clearQuery(); tempData()->unregisterForTablesSchemaChanges(); for (TQStringList::const_iterator it = sortedTableNames.constBegin(); it!=sortedTableNames.constEnd(); ++it) { //table /*! @todo what about query? */ KexiDB::TableSchema *table = d->relations->tables()->tqfind(*it)->schema()->table(); d->conn->registerForTableSchemaChanges(*tempData(), *table); //this table will be used item = d->tablesColumnData->createItem(); //new KexiTableItem(2); (*item)[COLUMN_ID_COLUMN]=table->name(); (*item)[COLUMN_ID_TABLE]=(*item)[COLUMN_ID_COLUMN]; d->tablesColumnData->append( item ); //fields item = d->fieldColumnData->createItem(); //new KexiTableItem(2); (*item)[COLUMN_ID_COLUMN]=table->name()+".*"; (*item)[COLUMN_ID_TABLE]=(*item)[COLUMN_ID_COLUMN]; d->fieldColumnData->append( item ); d->fieldColumnIdentifiers.insert((*item)[COLUMN_ID_COLUMN].toString(), (char*)1); //cache for (KexiDB::Field::ListIterator t_it = table->fieldsIterator();t_it.current();++t_it) { item = d->fieldColumnData->createItem(); // new KexiTableItem(2); (*item)[COLUMN_ID_COLUMN]=table->name()+"."+t_it.current()->name(); (*item)[COLUMN_ID_TABLE]=TQString(" ") + t_it.current()->name(); d->fieldColumnData->append( item ); d->fieldColumnIdentifiers.insert((*item)[COLUMN_ID_COLUMN].toString(), (char*)1); //cache } } //TODO } KexiRelationWidget *KexiQueryDesignerGuiEditor::relationView() const { return d->relations; } KexiQueryPart::TempData * KexiQueryDesignerGuiEditor::tempData() const { return static_cast(parentDialog()->tempData()); } static TQString msgCannotSwitch_EmptyDesign() { return i18n("Cannot switch to data view, because query design is empty.\n" "First, please create your design."); } bool KexiQueryDesignerGuiEditor::buildSchema(TQString *errMsg) { //build query schema KexiQueryPart::TempData * temp = tempData(); if (temp->query()) { temp->clearQuery(); } else { temp->setQuery( new KexiDB::QuerySchema() ); } //add tables for (TablesDictIterator it(*d->relations->tables()); it.current(); ++it) { /*! @todo what about query? */ temp->query()->addTable( it.current()->schema()->table() ); } //add fields, also build: // -WHERE expression // -ORDER BY list KexiDB::BaseExpr *whereExpr = 0; const uint count = TQMIN(d->data->count(), d->sets->size()); bool fieldsFound = false; KexiTableViewData::Iterator it(d->data->iterator()); for (uint i=0; iat(COLUMN_ID_TABLE).isNull() && it.current()->at(COLUMN_ID_COLUMN).isNull()) { //show message about missing field name, and set focus to that cell kexipluginsdbg << "no field provided!" << endl; d->dataTable->dataAwareObject()->setCursorPosition(i,0); if (errMsg) *errMsg = i18n("Select column for table \"%1\"") .tqarg(it.current()->at(COLUMN_ID_TABLE).toString()); return false; } KoProperty::Set *set = d->sets->at(i); if (set) { TQString tableName = (*set)["table"].value().toString().stripWhiteSpace(); TQString fieldName = (*set)["field"].value().toString(); TQString fieldAndTableName = fieldName; KexiDB::Field *currentField = 0; // will be set if this column is a single field KexiDB::QueryColumnInfo* currentColumn = 0; if (!tableName.isEmpty()) fieldAndTableName.prepend(tableName+"."); const bool fieldVisible = (*set)["visible"].value().toBool(); TQString criteriaStr = (*set)["criteria"].value().toString(); TQCString alias = (*set)["alias"].value().toCString(); if (!criteriaStr.isEmpty()) { int token; KexiDB::BaseExpr *criteriaExpr = parseExpressionString(criteriaStr, token, true/*allowRelationalOperator*/); if (!criteriaExpr) {//for sanity if (errMsg) *errMsg = i18n("Invalid criteria \"%1\"").tqarg(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\"").tqarg(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) { kexipluginswarn << "query designer: NO TABLE '" << (*set)["table"].value().toString() << "'" << endl; continue; } currentField = t->field( fieldName ); if (!currentField) { kexipluginswarn << "query designer: NO FIELD '" << fieldName << "'" << endl; continue; } if (!fieldVisible && criteriaStr.isEmpty() && (*set)["isExpression"] && (*set)["sorting"].value().toString()!="nosorting") { kexipluginsdbg << "invisible field with sorting: do not add it to the fields list" << endl; continue; } temp->query()->addField(currentField, fieldVisible); currentColumn = temp->query()->expandedOrInternalField( temp->query()->fieldsExpanded().count() - 1 ); if (fieldVisible) fieldsFound = true; if (!alias.isEmpty()) temp->query()->setColumnAlias( temp->query()->fieldCount()-1, alias ); } } } else {//!set kexipluginsdbg << it.current()->at(COLUMN_ID_TABLE).toString() << endl; } } if (!fieldsFound) { if (errMsg) *errMsg = msgCannotSwitch_EmptyDesign(); return false; } if (whereExpr) kexipluginsdbg << "KexiQueryDesignerGuiEditor::buildSchema(): setting CRITERIA: " << whereExpr->debugString() << endl; //set always, because if whereExpr==NULL, //this will clear prev. expr temp->query()->setWhereExpression( whereExpr ); //add relations (looking for connections) for (ConnectionListIterator it(*d->relations->connections()); it.current(); ++it) { KexiRelationViewTableContainer *masterTable = it.current()->masterTable(); KexiRelationViewTableContainer *detailsTable = it.current()->detailsTable(); /*! @todo what about query? */ temp->query()->addRelationship( masterTable->schema()->table()->field(it.current()->masterField()), detailsTable->schema()->table()->field(it.current()->detailsField()) ); } // Add sorting information (ORDER BY) - we can do that only now // after all QueryColumnInfo items are instantiated KexiDB::OrderByColumnList orderByColumns; it = d->data->iterator(); int fieldNumber = -1; //field number (empty rows are omitted) for (uint i=0/*row number*/; isets->at(i); if (!set) continue; fieldNumber++; KexiDB::Field *currentField = 0; KexiDB::QueryColumnInfo *currentColumn = 0; TQString 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) { kexipluginswarn << "KexiQueryDesignerGuiEditor::buildSchema(): NO FIELD '" << (*set)["field"].value().toString() << " available for sorting" << endl; 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 TQString 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(int mode, bool &dontStore) { kexipluginsdbg << "KexiQueryDesignerGuiEditor::beforeSwitch()" << mode << endl; if (!d->dataTable->dataAwareObject()->acceptRowEdit()) return cancelled; if (mode==Kexi::DesignViewMode) { return true; } else if (mode==Kexi::DataViewMode) { // if (!d->dataTable->dataAwareObject()->acceptRowEdit()) // return cancelled; if (!dirty() && parentDialog()->neverSaved()) { KMessageBox::information(this, msgCannotSwitch_EmptyDesign()); return cancelled; } if (dirty() || !tempData()->query()) { //remember current design in a temporary structure dontStore=true; TQString errMsg; //build schema; problems are not allowed if (!buildSchema(&errMsg)) { KMessageBox::sorry(this, errMsg); return cancelled; } } //TODO return true; } else if (mode==Kexi::TextViewMode) { dontStore=true; //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(int mode) { const bool was_dirty = dirty(); KexiDB::Connection *conn = parentDialog()->mainWin()->project()->dbConnection(); if (mode==Kexi::NoViewMode || (mode==Kexi::DataViewMode && !tempData()->query())) { //this is not a SWITCH but a fresh opening in this view mode if (!m_dialog->neverSaved()) { if (!loadLayout()) { //err msg parentDialog()->settqStatus(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: // KexiDialogBase::switchToViewMode() first opens DesignViewMode, // and then KexiQueryPart::loadSchemaData() doesn't allocate QuerySchema object // do we're carefully looking at parentDialog()->schemaData() KexiDB::QuerySchema * q = dynamic_cast(parentDialog()->schemaData()); if (q) { KexiDB::ResultInfo result; showFieldsForQuery( q, result ); if (!result.success) { parentDialog()->settqStatus(&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 //-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) { parentDialog()->settqStatus(&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); } } tempData()->queryChangedInPreviousView = false; setFocus(); //to allow shared actions proper update if (!was_dirty) setDirty(false); return true; } KexiDB::SchemaData* KexiQueryDesignerGuiEditor::storeNewData(const KexiDB::SchemaData& sdata, bool &cancel) { if (!d->dataTable->dataAwareObject()->acceptRowEdit()) { cancel = true; return 0; } TQString errMsg; KexiQueryPart::TempData * temp = tempData(); if (!temp->query() || !(viewMode()==Kexi::DesignViewMode && !temp->queryChangedInPreviousView)) { //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 = m_mainWin->project()->dbConnection()->storeObjectSchemaData( *temp->query(), true /*newObject*/ ); m_dialog->setId( temp->query()->id() ); if (ok) ok = storeLayout(); // temp->query = 0; //will be returned, so: don't keep it if (!ok) { temp->setQuery( 0 ); // delete query; 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 = dirty(); tristate res = KexiViewBase::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) { //replaced by code below that preserves geometries d->relations->clear(); // 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() ); for (KexiDB::TableSchema::ListIterator it(*query->tables()); it.current(); ++it) { d->relations->addTable( it.current() ); } 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 = dirty(); //1. Show explicitly declared relations: if (showRelations) { KexiDB::Relationship *rel; for (KexiDB::Relationship::ListIterator it(*query->relationships()); (rel=it.current()); ++it) { //! @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 TQDict criterias(101, false); 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; kexidbg << eItem->toString() << endl; 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() )) && (rightField = query->findTableField( binary->right()->toString() ))) { //! @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.insert(binary->left()->toVariable()->name, binary->right()); } else if (binary->right()->toVariable()) { //this is: argument , op , variable //store variable -> argument: criterias.insert(binary->right()->toVariable()->name, binary->left()); } } } //while if (!showFields) return; //3. show fields (including * and table.*) uint row_num = 0; KexiDB::Field *field; TQPtrDict usedCriterias(101); // <-- used criterias will be saved here // so in step 4. we will be able to add // remaining invisible columns with criterias for (KexiDB::Field::ListIterator it(*query->fields()); (field = it.current()); ++it, row_num++) { //append a new row TQString 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()) { // if (columnAlias.isEmpty()) { // columnAlias = i18n("expression", "expr%1").tqarg(row_num); //TODO // } // if (columnAlias.isEmpty()) //TODO: ok? perhaps do not allow to omit aliases? fieldName = field->expression()->toString(); // else // fieldName = columnAlias + ": " + field->expression()->toString(); } else { tableName = field->table()->name(); fieldName = field->name(); criteriaArgument = criterias[fieldName]; if (!criteriaArgument) {//try table.field criteriaArgument = criterias[tableName+"."+fieldName]; } if (criteriaArgument) {//criteria expression is just a tqparent of argument criteriaExpr = criteriaArgument->tqparent()->toBinary(); usedCriterias.insert(criteriaArgument, (char*)1); //save info. about used criteria } } } //create new row data KexiTableItem *newItem = createNewRow(tableName, fieldName, true /* visible*/); if (criteriaExpr) { //! @todo fix for !INFIX operators if (criteriaExpr->token()=='=') criteriaString = criteriaArgument->toString(); else criteriaString = criteriaExpr->tokenToString() + " " + criteriaArgument->toString(); (*newItem)[COLUMN_ID_CRITERIA] = criteriaString; } d->dataTable->dataAwareObject()->insertItem(newItem, 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()) { // (*newItem)[COLUMN_ID_COLUMN] = ; if (!d->changeSingleCellValue(*newItem, COLUMN_ID_COLUMN, TQVariant(columnAlias + ": " + field->expression()->toString()), &result)) return; //problems with setting column expression } } //4. show ORDER BY information d->data->clearRowEditBuffer(); KexiDB::OrderByColumnList &orderByColumns = query->orderByColumnList(); TQMap columnsOrder( query->columnsOrder(KexiDB::QuerySchema::UnexpandedListWithoutAsterisks) ); for (KexiDB::OrderByColumn::ListConstIterator orderByColumnsIt( orderByColumns.constBegin() ); orderByColumnsIt!=orderByColumns.constEnd(); ++orderByColumnsIt) { KexiDB::QueryColumnInfo *column = (*orderByColumnsIt).column(); KexiTableItem *rowItem = 0; KoProperty::Set *rowPropertySet = 0; if (column) { //sorting for visible column if (column->visible) { if (columnsOrder.tqcontains(column)) { const int columnPosition = columnsOrder[ column ]; rowItem = d->data->at( columnPosition ); rowPropertySet = d->sets->at( columnPosition ); kexipluginsdbg << "KexiQueryDesignerGuiEditor::showFieldsOrRelationsForQueryInternal():\n\t" "Setting \"" << (*orderByColumnsIt).debugString() << "\" sorting for row #" << columnPosition << endl; } } } else if ((*orderByColumnsIt).field()) { //this will be presented as invisible field: create new row field = (*orderByColumnsIt).field(); TQString tableName( field->table() ? field->table()->name() : TQString() ); rowItem = createNewRow( tableName, field->name(), false /* !visible*/); d->dataTable->dataAwareObject()->insertItem(rowItem, row_num); rowPropertySet = createPropertySet( row_num, tableName, field->name(), true /*newOne*/ ); propertySetSwitched(); kexipluginsdbg << "KexiQueryDesignerGuiEditor::showFieldsOrRelationsForQueryInternal():\n\t" "Setting \"" << (*orderByColumnsIt).debugString() << "\" sorting for invisible field " << field->name() << ", table " << tableName << " -row #" << row_num << endl; row_num++; } //alter sorting for either existing or new row if (rowItem && rowPropertySet) { d->data->updateRowEditBuffer(rowItem, COLUMN_ID_SORTING, (*orderByColumnsIt).ascending() ? 1 : 2); // this will automatically update "sorting" property // in slotBeforeCellChanged() d->data->saveRowChanges(*rowItem, true); (*rowPropertySet)["sorting"].clearModifiedFlag(); // this property should look "fresh" if (!rowItem->at(COLUMN_ID_VISIBLE).toBool()) //update (*rowPropertySet)["visible"].setValue(TQVariant(false,0), false/*rememberOldValue*/); } } //5. Show fields for unused criterias (with "Visible" column set to false) KexiDB::BaseExpr *criteriaArgument; // <-- contains field or table.field for (TQDictIterator it(criterias); (criteriaArgument = it.current()); ++it) { if (usedCriterias[it.current()]) continue; //unused: append a new row KexiDB::BinaryExpr *criteriaExpr = criteriaArgument->tqparent()->toBinary(); if (!criteriaExpr) { kexipluginswarn << "KexiQueryDesignerGuiEditor::showFieldsOrRelationsForQueryInternal(): " "criteriaExpr is not a binary expr" << endl; continue; } KexiDB::VariableExpr *columnNameArgument = criteriaExpr->left()->toVariable(); //left or right if (!columnNameArgument) { columnNameArgument = criteriaExpr->right()->toVariable(); if (!columnNameArgument) { kexipluginswarn << "KexiQueryDesignerGuiEditor::showFieldsOrRelationsForQueryInternal(): " "columnNameArgument is not a variable (table or table.field) expr" << endl; continue; } } KexiDB::Field* field = 0; if (-1 == columnNameArgument->name.tqfind('.') && 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) { kexipluginswarn << "KexiQueryDesignerGuiEditor::showFieldsOrRelationsForQueryInternal(): " "no columnInfo found in the query for name \"" << columnNameArgument->name << endl; continue; } TQString tableName, fieldName, columnAlias, criteriaString; //! @todo what about ALIAS? tableName = field->table()->name(); fieldName = field->name(); //create new row data KexiTableItem *newItem = createNewRow(tableName, fieldName, false /* !visible*/); if (criteriaExpr) { //! @todo fix for !INFIX operators if (criteriaExpr->token()=='=') criteriaString = criteriaArgument->toString(); else criteriaString = criteriaExpr->tokenToString() + " " + criteriaArgument->toString(); (*newItem)[COLUMN_ID_CRITERIA] = criteriaString; } d->dataTable->dataAwareObject()->insertItem(newItem, 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( TQVariant(false,1), 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() { TQString xml; // if (!loadDataBlock( xml, "query_layout" )) { loadDataBlock( xml, "query_layout" ); //TODO errmsg // return false; // } if (xml.isEmpty()) { //in a case when query tqlayout was not saved, build tqlayout by hand // -- dynamic cast because of a need for handling invalid queries // (as in KexiQueryDesignerGuiEditor::afterSwitchFrom()): KexiDB::QuerySchema * q = dynamic_cast(parentDialog()->schemaData()); if (q) { showTablesForQuery( q ); KexiDB::ResultInfo result; showRelationsForQuery( q, result ); if (!result.success) { parentDialog()->settqStatus(&result, i18n("Query definition loading failed.")); return false; } } return true; } TQDomDocument doc; doc.setContent(xml); TQDomElement doc_el = doc.documentElement(), el; if (doc_el.tagName()!="query_layout") { //TODO errmsg return false; } const bool was_dirty = dirty(); //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(); TQRect rect; if (x!=-1 || y!=-1 || width!=-1 || height!=-1) rect = TQRect(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. KexiDB::Connection* dbConn = mainWin()->project()->dbConnection(); if (m_dialog->schemaData()) //set this instance as obsolete (only if it's stored) dbConn->setQuerySchemaObsolete( m_dialog->schemaData()->name() ); KexiDB::Connection::SelectStatementOptions options; options.identifierEscaping = KexiDB::Driver::EscapeKexi|KexiDB::Driver::EscapeAsNecessary; options.addVisibleLookupColumns = false; TQString sqlText = dbConn->selectStatement( *temp->query(), options ); if (!storeDataBlock( sqlText, "sql" )) { return false; } //serialize detailed XML query definition TQString xml = "", tmp; for (TablesDictIterator it(*d->relations->tables()); it.current(); ++it) { KexiRelationViewTableContainer *table_cont = it.current(); /*! @todo what about query? */ tmp = TQString("schema()->name())+"\" x=\"" +TQString::number(table_cont->x()) +"\" y=\""+TQString::number(table_cont->y()) +"\" width=\""+TQString::number(table_cont->width()) +"\" height=\""+TQString::number(table_cont->height()) +"\"/>"; xml += tmp; } KexiRelationViewConnection *con; for (ConnectionListIterator it(*d->relations->connections()); (con = it.current()); ++it) { tmp = TQString("masterTable()->schema()->name()) + "\" mfield=\"" + con->masterField() + "\" dtable=\"" + TQString(con->detailsTable()->schema()->name()) + "\" dfield=\"" + con->detailsField() + "\"/>"; xml += tmp; } xml += ""; if (!storeDataBlock( xml, "query_layout" )) { return false; } // mainWin()->project()->reloadPartItem( m_dialog ); return true; } TQSize KexiQueryDesignerGuiEditor::tqsizeHint() const { TQSize s1 = d->relations->tqsizeHint(); TQSize s2 = d->head->tqsizeHint(); return TQSize(TQMAX(s1.width(),s2.width()), s1.height()+s2.height()); } KexiTableItem* KexiQueryDesignerGuiEditor::createNewRow(const TQString& tableName, const TQString& fieldName, bool visible) const { KexiTableItem *newItem = d->data->createItem(); TQString key; if (tableName=="*") key="*"; else { if (!tableName.isEmpty()) key = (tableName+"."); key += fieldName; } (*newItem)[COLUMN_ID_COLUMN]=key; (*newItem)[COLUMN_ID_TABLE]=tableName; (*newItem)[COLUMN_ID_VISIBLE]=TQVariant(visible, 1); #ifndef KEXI_NO_TQUERY_TOTALS (*newItem)[COLUMN_ID_TOTALS]=TQVariant(0); #endif return newItem; } void KexiQueryDesignerGuiEditor::slotDragOverTableRow( KexiTableItem * /*item*/, int /*row*/, TQDragMoveEvent* e) { if (e->provides("kexi/field")) { e->acceptAction(true); } } void KexiQueryDesignerGuiEditor::slotDroppedAtRow(KexiTableItem * /*item*/, int /*row*/, TQDropEvent *ev, KexiTableItem*& newItem) { TQString sourceMimeType; TQString srcTable; TQString srcField; if (!KexiFieldDrag::decodeSingle(ev,sourceMimeType,srcTable,srcField)) return; //insert new row at specific place newItem = createNewRow(srcTable, srcField, true /* visible*/); d->droppedNewItem = newItem; d->droppedNewTable = srcTable; d->droppedNewField = srcField; //TODO } void KexiQueryDesignerGuiEditor::slotNewItemAppendedForAfterDeletingInSpreadSheetMode() { KexiTableItem *item = d->data->last(); if (item) item->at(COLUMN_ID_VISIBLE) = TQVariant(false, 0); //the same init as in initTableRows() } void KexiQueryDesignerGuiEditor::slotRowInserted(KexiTableItem* item, uint row, bool /*tqrepaint*/) { if (d->droppedNewItem && d->droppedNewItem==item) { createPropertySet( row, d->droppedNewTable, d->droppedNewField, true ); propertySetSwitched(); d->droppedNewItem=0; } } void KexiQueryDesignerGuiEditor::slotTableAdded(KexiDB::TableSchema & /*t*/) { if (!d->slotTableAdded_enabled) return; updateColumnsData(); setDirty(); d->dataTable->setFocus(); } void KexiQueryDesignerGuiEditor::slotTableHidden(KexiDB::TableSchema & /*t*/) { updateColumnsData(); setDirty(); } /*! @internal generates smallest unique alias */ TQCString KexiQueryDesignerGuiEditor::generateUniqueAlias() const { //TODO: add option for using non-i18n'd "expr" prefix? const TQCString expStr = i18n("short for 'expression' word (only latin letters, please)", "expr").latin1(); //TODO: optimization: cache it? TQAsciiDict aliases(101); for (int r = 0; r<(int)d->sets->size(); r++) { KoProperty::Set *set = d->sets->at(r); if (set) { const TQCString a = (*set)["alias"].value().toCString().lower(); if (!a.isEmpty()) aliases.insert(a,(char*)1); } } int aliasNr=1; for (;;aliasNr++) { if (!aliases[expStr+TQString::number(aliasNr).latin1()]) break; } return expStr+TQString::number(aliasNr).latin1(); } //! @todo this is primitive, temporary: reuse SQL parser KexiDB::BaseExpr* KexiQueryDesignerGuiEditor::parseExpressionString(const TQString& fullString, int& token, bool allowRelationalOperator) { TQString str = fullString.stripWhiteSpace(); int len = 0; //KexiDB::BaseExpr *expr = 0; //1. get token token = 0; //2-char-long tokens if (str.startsWith(">=")) token = GREATER_OR_EQUAL; else if (str.startsWith("<=")) token = LESS_OR_EQUAL; else if (str.startsWith("<>")) token = NOT_EQUAL; else if (str.startsWith("!=")) token = NOT_EQUAL2; else if (str.startsWith("==")) token = '='; if (token!=0) len = 2; else if (str.startsWith("=") //1-char-long tokens || str.startsWith("<") || str.startsWith(">")) { token = str[0].latin1(); len = 1; } else { if (allowRelationalOperator) token = '='; } if (!allowRelationalOperator && token!=0) return 0; //1. get expression after token if (len>0) str = str.mid(len).stripWhiteSpace(); if (str.isEmpty()) return 0; KexiDB::BaseExpr *valueExpr = 0; TQRegExp re; if (str.length()>=2 && ( (str.startsWith("\"") && str.endsWith("\"")) || (str.startsWith("'") && str.endsWith("'"))) ) { valueExpr = new KexiDB::ConstExpr(CHARACTER_STRING_LITERAL, str.mid(1,str.length()-2)); } else if (str.startsWith("[") && str.endsWith("]")) { valueExpr = new KexiDB::QueryParameterExpr(str.mid(1,str.length()-2)); } else if ((re = TQRegExp("(\\d{1,4})-(\\d{1,2})-(\\d{1,2})")).exactMatch( str )) { valueExpr = new KexiDB::ConstExpr(DATE_CONST, TQDate::fromString( re.cap(1).rightJustify(4, '0')+"-"+re.cap(2).rightJustify(2, '0') +"-"+re.cap(3).rightJustify(2, '0'), Qt::ISODate)); } else if ((re = TQRegExp("(\\d{1,2}):(\\d{1,2})")).exactMatch( str ) || (re = TQRegExp("(\\d{1,2}):(\\d{1,2}):(\\d{1,2})")).exactMatch( str )) { TQString res = re.cap(1).rightJustify(2, '0')+":"+re.cap(2).rightJustify(2, '0') +":"+re.cap(3).rightJustify(2, '0'); // kexipluginsdbg << res << endl; valueExpr = new KexiDB::ConstExpr(TIME_CONST, TQTime::fromString(res, Qt::ISODate)); } else if ((re = TQRegExp("(\\d{1,4})-(\\d{1,2})-(\\d{1,2})\\s+(\\d{1,2}):(\\d{1,2})")).exactMatch( str ) || (re = TQRegExp("(\\d{1,4})-(\\d{1,2})-(\\d{1,2})\\s+(\\d{1,2}):(\\d{1,2}):(\\d{1,2})")).exactMatch( str )) { TQString res = re.cap(1).rightJustify(4, '0')+"-"+re.cap(2).rightJustify(2, '0') +"-"+re.cap(3).rightJustify(2, '0') +"T"+re.cap(4).rightJustify(2, '0')+":"+re.cap(5).rightJustify(2, '0') +":"+re.cap(6).rightJustify(2, '0'); // kexipluginsdbg << res << endl; valueExpr = new KexiDB::ConstExpr(DATETIME_CONST, TQDateTime::fromString(res, Qt::ISODate)); } else if (str[0]>='0' && str[0]<='9' || str[0]=='-' || str[0]=='+') { //number TQString decimalSym = KGlobal::locale()->decimalSymbol(); bool ok; int pos = str.tqfind('.'); if (pos==-1) {//second chance: local decimal symbol pos = str.tqfind(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, TQPoint(left,right)); //decoded to TQPoint } else { //integer const const TQ_LLONG val = str.toLongLong(&ok); if (!ok) return 0; valueExpr = new KexiDB::ConstExpr(INTEGER_CONST, val); } } else if (str.lower()=="null") { valueExpr = new KexiDB::ConstExpr(SQL_NULL, TQVariant()); } else {//identfier if (!KexiUtils::isIdentifier(str)) return 0; valueExpr = new KexiDB::VariableExpr(str); //find first matching field for name 'str': for (TablesDictIterator it(*d->relations->tables()); it.current(); ++it) { /*! @todo what about query? */ if (it.current()->schema()->table() && it.current()->schema()->table()->field(str)) { valueExpr->toVariable()->field = it.current()->schema()->table()->field(str); break; } } } return valueExpr; } void KexiQueryDesignerGuiEditor::slotBeforeCellChanged(KexiTableItem *item, int colnum, TQVariant& newValue, KexiDB::ResultInfo* result) { if (colnum == COLUMN_ID_COLUMN) { if (newValue.isNull()) { d->data->updateRowEditBuffer(item, COLUMN_ID_TABLE, TQVariant(), false/*!allowSignals*/); d->data->updateRowEditBuffer(item, COLUMN_ID_VISIBLE, TQVariant(false,1));//invisible d->data->updateRowEditBuffer(item, COLUMN_ID_SORTING, TQVariant()); #ifndef KEXI_NO_TQUERY_TOTALS d->data->updateRowEditBuffer(item, COLUMN_ID_TOTALS, TQVariant());//remove totals #endif d->data->updateRowEditBuffer(item, COLUMN_ID_CRITERIA, TQVariant());//remove crit. d->sets->removeCurrentPropertySet(); } else { //auto fill 'table' column TQString fieldId( newValue.toString().stripWhiteSpace() ); //tmp, can look like "table.field" TQString fieldName; //"field" part of "table.field" or expression string TQString tableName; //empty for expressions TQCString alias; TQString columnValueForExpr; //for setting pretty printed "alias: expr" in 1st column const bool isExpression = !d->fieldColumnIdentifiers[fieldId]; 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 //-tqfind "alias" in something like "alias : expr" const int id = fieldId.tqfind(':'); if (id>0) { alias = fieldId.left(id).stripWhiteSpace().latin1(); if (!KexiUtils::isIdentifier(alias)) { result->success = false; result->allowToDiscardChanges = true; result->column = colnum; result->msg = i18n("Entered column alias \"%1\" is not a valid identifier.") .tqarg(alias.data()); result->desc = i18n("Identifiers should start with a letter or '_' character"); return; } } fieldName = fieldId.mid(id+1).stripWhiteSpace(); //check expr. KexiDB::BaseExpr *e; int dummyToken; if ((e = parseExpressionString(fieldName, dummyToken, false/*allowRelationalOperator*/))) { fieldName = e->toString(); //print it prettier //this is just checking: destroy expr. object delete e; } else { result->success = false; result->allowToDiscardChanges = true; result->column = colnum; result->msg = i18n("Invalid expression \"%1\"").tqarg(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)) { kexipluginswarn << "KexiQueryDesignerGuiEditor::slotBeforeCellChanged(): no 'field' or 'table.field'" << endl; return; } } } bool saveOldValue = true; KoProperty::Set *set = d->sets->findPropertySetForItem(*item); //*propertyBuffer(); if (!set) { saveOldValue = false; // no old val. const int row = d->data->tqfindRef(item); if (row<0) { result->success = false; return; } set = createPropertySet( row, tableName, fieldName, true ); propertySetSwitched(); } d->data->updateRowEditBuffer(item, COLUMN_ID_TABLE, TQVariant(tableName), false/*!allowSignals*/); d->data->updateRowEditBuffer(item, COLUMN_ID_VISIBLE, TQVariant(true,1)); #ifndef KEXI_NO_TQUERY_TOTALS d->data->updateRowEditBuffer(item, COLUMN_ID_TOTALS, TQVariant(0)); #endif if (!sortingAllowed(fieldName, tableName)) { // sorting is not available for "*" or "table.*" rows //! @todo what about expressions? d->data->updateRowEditBuffer(item, COLUMN_ID_SORTING, TQVariant()); } //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().toCString(); if (alias.isEmpty()) //-generate smallest unique alias alias = generateUniqueAlias(); } (*set)["isExpression"].setValue(TQVariant(isExpression,1), saveOldValue); if (!alias.isEmpty()) { (*set)["alias"].setValue(alias, saveOldValue); //pretty printed "alias: expr" newValue = TQString(alias) + ": " + fieldName; } (*set)["caption"].setValue(TQString(), saveOldValue); (*set)["table"].setValue(tableName, saveOldValue); updatePropertiesVisibility(*set); } } else if (colnum==COLUMN_ID_TABLE) { if (newValue.isNull()) { if (!item->at(COLUMN_ID_COLUMN).toString().isEmpty()) d->data->updateRowEditBuffer(item, COLUMN_ID_COLUMN, TQVariant(), false/*!allowSignals*/); d->data->updateRowEditBuffer(item, COLUMN_ID_VISIBLE, TQVariant(false,1));//invisible #ifndef KEXI_NO_TQUERY_TOTALS d->data->updateRowEditBuffer(item, COLUMN_ID_TOTALS, TQVariant());//remove totals #endif d->data->updateRowEditBuffer(item, COLUMN_ID_CRITERIA, TQVariant());//remove crit. d->sets->removeCurrentPropertySet(); } //update property KoProperty::Set *set = d->sets->findPropertySetForItem(*item); if (set) { if ((*set)["isExpression"].value().toBool()==false) { (*set)["table"] = newValue; (*set)["caption"] = TQString(); } else { //do not set table for expr. columns newValue = TQVariant(); } // KoProperty::Set &set = *propertyBuffer(); updatePropertiesVisibility(*set); } } else if (colnum==COLUMN_ID_VISIBLE) { bool saveOldValue = true; if (!propertySet()) { saveOldValue = false; createPropertySet( d->dataTable->dataAwareObject()->currentRow(), item->at(COLUMN_ID_TABLE).toString(), item->at(COLUMN_ID_COLUMN).toString(), true ); #ifndef KEXI_NO_TQUERY_TOTALS d->data->updateRowEditBuffer(item, COLUMN_ID_TOTALS, TQVariant(0));//totals #endif propertySetSwitched(); } KoProperty::Set &set = *propertySet(); set["visible"].setValue(newValue, saveOldValue); } #ifndef KEXI_NO_TQUERY_TOTALS else if (colnum==COLUMN_ID_TOTALS) { //TODO: //unused yet setDirty(true); } #endif else if (colnum==COLUMN_ID_SORTING) { KoProperty::Set *set = d->sets->findPropertySetForItem(*item); TQString table( set->property("table").value().toString() ); TQString field( set->property("field").value().toString() ); if (newValue.toInt()==0 || sortingAllowed(field, table)) { KoProperty::Property &property = set->property("sorting"); TQString key( property.listData()->keysAsStringList()[ newValue.toInt() ] ); kexipluginsdbg << "new key=" << key << endl; property.setValue(key, true); } else { //show msg: sorting is not available result->success = false; result->allowToDiscardChanges = true; result->column = colnum; result->msg = i18n("Could not set sorting for multiple columns (%1)") .tqarg(table=="*" ? table : (table+".*")); } } else if (colnum==COLUMN_ID_CRITERIA) { //! @todo this is primitive, temporary: reuse SQL parser TQString operatorStr, argStr; KexiDB::BaseExpr* e = 0; const TQString str = newValue.toString().stripWhiteSpace(); int token; TQString field, table; KoProperty::Set *set = d->sets->findPropertySetForItem(*item); if (set) { field = (*set)["field"].value().toString(); table = (*set)["table"].value().toString(); } if (!str.isEmpty() && (!set || table=="*" || field.tqfind("*")!=-1)) { //asterisk found! criteria not allowed result->success = false; result->allowToDiscardChanges = true; result->column = colnum; if (propertySet()) result->msg = i18n("Could not set criteria for \"%1\"") .tqarg(table=="*" ? table : field); else result->msg = i18n("Could not set criteria for empty row"); //moved to result->allowToDiscardChanges handler //d->dataTable->dataAwareObject()->cancelEditor(); //prevents further editing of this cell } else if (str.isEmpty() || (e = parseExpressionString(str, token, true/*allowRelationalOperator*/))) { if (e) { TQString tokenStr; if (token!='=') { KexiDB::BinaryExpr be(KexiDBExpr_Relational, 0, token, 0); tokenStr = be.tokenToString() + " "; } (*set)["criteria"] = tokenStr + e->toString(); //print it prettier //this is just checking: destroy expr. object delete e; } else if (str.isEmpty()) { (*set)["criteria"] = TQVariant(); //clear it } setDirty(true); } else { result->success = false; result->allowToDiscardChanges = true; result->column = colnum; result->msg = i18n("Invalid criteria \"%1\"").tqarg(newValue.toString()); } } } void KexiQueryDesignerGuiEditor::slotTablePositionChanged(KexiRelationViewTableContainer*) { setDirty(true); } void KexiQueryDesignerGuiEditor::slotAboutConnectionRemove(KexiRelationViewConnection*) { setDirty(true); } void KexiQueryDesignerGuiEditor::slotTableFieldDoubleClicked( KexiDB::TableSchema* table, const TQString& fieldName ) { if (!table || (!table->field(fieldName) && 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 KexiTableItem *newItem = createNewRow(table->name(), fieldName, true /* visible*/); d->dataTable->dataAwareObject()->insertItem(newItem, 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() ); #ifndef KEXI_NO_UNFINISHED set["caption"].setVisible( !asterisk ); #endif set["alias"].setVisible( !asterisk ); /*always invisible #ifndef KEXI_NO_UNFINISHED set["sorting"].setVisible( !asterisk ); #endif*/ propertySetReloaded(true); } KoProperty::Set* KexiQueryDesignerGuiEditor::createPropertySet( int row, const TQString& tableName, const TQString& fieldName, bool newOne ) { //const bool asterisk = isAsterisk(tableName, fieldName); TQString 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", TQVariant(tableName)) ); prop->setVisible(false);//always hidden set->addProperty(prop = new KoProperty::Property("field", TQVariant(fieldName)) ); prop->setVisible(false);//always hidden set->addProperty(prop = new KoProperty::Property("caption", TQVariant(TQString()), i18n("Caption") ) ); #ifdef KEXI_NO_UNFINISHED prop->setVisible(false); #endif set->addProperty(prop = new KoProperty::Property("alias", TQVariant(TQString()), i18n("Alias")) ); set->addProperty(prop = new KoProperty::Property("visible", TQVariant(true, 4)) ); prop->setVisible(false); /*TODO: set->addProperty(prop = new KexiProperty("totals", TQVariant(TQString())) ); prop->setVisible(false);*/ //sorting TQStringList slist, nlist; slist << "nosorting" << "ascending" << "descending"; nlist << i18n("None") << i18n("Ascending") << i18n("Descending"); set->addProperty(prop = new KoProperty::Property("sorting", slist, nlist, *slist.at(0), i18n("Sorting"))); prop->setVisible(false); set->addProperty(prop = new KoProperty::Property("criteria", TQVariant(TQString())) ); prop->setVisible(false); set->addProperty(prop = new KoProperty::Property("isExpression", TQVariant(false, 1)) ); prop->setVisible(false); connect(set, TQT_SIGNAL(propertyChanged(KoProperty::Set&, KoProperty::Property&)), this, TQT_SLOT(slotPropertyChanged(KoProperty::Set&, KoProperty::Property&))); d->sets->insert(row, set, newOne); updatePropertiesVisibility(*set); return set; } void KexiQueryDesignerGuiEditor::setFocus() { d->dataTable->setFocus(); } void KexiQueryDesignerGuiEditor::slotPropertyChanged(KoProperty::Set& set, KoProperty::Property& property) { const TQCString& pname = property.name(); /* * TODO (js) use KexiProperty::setValidator(TQString) when implemented as described in TODO #60 */ if (pname=="alias" || pname=="name") { const TQVariant& v = property.value(); if (!v.toString().stripWhiteSpace().isEmpty() && !KexiUtils::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->dataTable->dataAwareObject()->setCursorPosition(d->dataTable->dataAwareObject()->currentRow(),0); //d->dataTable->dataAwareObject()->startEditCurrentCell(); d->data->updateRowEditBuffer(d->dataTable->dataAwareObject()->selectedItem(), 0, TQVariant(set["alias"].value().toString() + ": " + set["field"].value().toString())); d->data->saveRowChanges(*d->dataTable->dataAwareObject()->selectedItem(), true); // d->dataTable->dataAwareObject()->acceptRowEdit(); } } } } void KexiQueryDesignerGuiEditor::slotNewItemStored(KexiPart::Item& item) { d->relations->objectCreated(item.mimeType(), item.name().latin1()); } void KexiQueryDesignerGuiEditor::slotItemRemoved(const KexiPart::Item& item) { d->relations->objectDeleted(item.mimeType(), item.name().latin1()); } void KexiQueryDesignerGuiEditor::slotItemRenamed(const KexiPart::Item& item, const TQCString& oldName) { d->relations->objectRenamed(item.mimeType(), oldName, item.name().latin1()); } #include "kexiquerydesignerguieditor.moc"