kexi

kexiquerydesignerguieditor.cpp

00001 /* This file is part of the KDE project
00002    Copyright (C) 2004 Lucijan Busch <lucijan@kde.org>
00003    Copyright (C) 2004-2006 Jaroslaw Staniek <js@iidea.pl>
00004 
00005    This library is free software; you can redistribute it and/or
00006    modify it under the terms of the GNU Library General Public
00007    License as published by the Free Software Foundation; either
00008    version 2 of the License, or (at your option) any later version.
00009 
00010    This library is distributed in the hope that it will be useful,
00011    but WITHOUT ANY WARRANTY; without even the implied warranty of
00012    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00013    Library General Public License for more details.
00014 
00015    You should have received a copy of the GNU Library General Public License
00016    along with this library; see the file COPYING.LIB.  If not, write to
00017    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00018  * Boston, MA 02110-1301, USA.
00019 */
00020 
00021 #include "kexiquerydesignerguieditor.h"
00022 
00023 #include <qlayout.h>
00024 #include <qpainter.h>
00025 #include <qdom.h>
00026 #include <qregexp.h>
00027 
00028 #include <kdebug.h>
00029 #include <klocale.h>
00030 #include <kmessagebox.h>
00031 
00032 #include <kexidb/field.h>
00033 #include <kexidb/queryschema.h>
00034 #include <kexidb/connection.h>
00035 #include <kexidb/parser/parser.h>
00036 #include <kexidb/parser/sqlparser.h>
00037 #include <kexidb/utils.h>
00038 #include <kexidb/roweditbuffer.h>
00039 #include <kexiutils/identifier.h>
00040 #include <kexiproject.h>
00041 #include <keximainwindow.h>
00042 #include <kexiinternalpart.h>
00043 #include <kexitableview.h>
00044 #include <kexitableitem.h>
00045 #include <kexitableviewdata.h>
00046 #include <kexidragobjects.h>
00047 #include <kexidialogbase.h>
00048 #include <kexidatatable.h>
00049 #include <kexi.h>
00050 #include <kexisectionheader.h>
00051 #include <widget/tableview/kexidataawarepropertyset.h>
00052 #include <widget/relations/kexirelationwidget.h>
00053 #include <widget/relations/kexirelationviewtable.h>
00054 #include <koproperty/property.h>
00055 #include <koproperty/set.h>
00056 
00057 #include "kexiquerypart.h"
00058 
00060 #define KEXI_NO_QUERY_TOTALS
00061 
00063 #define COLUMN_ID_COLUMN 0
00064 #define COLUMN_ID_TABLE 1
00065 #define COLUMN_ID_VISIBLE 2
00066 #ifdef KEXI_NO_QUERY_TOTALS
00067 # define COLUMN_ID_SORTING 3
00068 # define COLUMN_ID_CRITERIA 4
00069 #else
00070 # define COLUMN_ID_TOTALS 3
00071 # define COLUMN_ID_SORTING 4
00072 # define COLUMN_ID_CRITERIA 5
00073 #endif
00074 
00076 class KexiQueryDesignerGuiEditor::Private
00077 {
00078 public:
00079     Private()
00080         : fieldColumnIdentifiers(101, false/*case insens.*/)
00081     {
00082         droppedNewItem = 0;
00083         slotTableAdded_enabled = true;
00084     }
00085 
00086     bool changeSingleCellValue(KexiTableItem &item, int columnNumber, 
00087         const QVariant& value, KexiDB::ResultInfo* result)
00088     {
00089         data->clearRowEditBuffer();
00090         if (!data->updateRowEditBuffer(&item, columnNumber, value)
00091             || !data->saveRowChanges(item, true))
00092         {
00093             if (result)
00094                 *result = *data->result();
00095             return false;
00096         }
00097         return true;
00098     }
00099 
00100     KexiTableViewData *data;
00101     KexiDataTable *dataTable;
00102     QGuardedPtr<KexiDB::Connection> conn;
00103 
00104     KexiRelationWidget *relations;
00105     KexiSectionHeader *head;
00106     QSplitter *spl;
00107 
00111     KexiTableViewData *fieldColumnData, *tablesColumnData;
00112 
00119     QDict<char> fieldColumnIdentifiers;
00120 
00121     KexiDataAwarePropertySet* sets;
00122     KexiTableItem *droppedNewItem;
00123 
00124     QString droppedNewTable, droppedNewField;
00125 
00126     bool slotTableAdded_enabled : 1;
00127 };
00128 
00129 static bool isAsterisk(const QString& tableName, const QString& fieldName)
00130 {
00131     return tableName=="*" || fieldName.endsWith("*");
00132 }
00133 
00135 static bool sortingAllowed(const QString& fieldName, const QString& tableName) {
00136     return ! (fieldName=="*" || (fieldName.isEmpty() && tableName=="*"));
00137 }
00138 
00139 //=========================================================
00140 
00141 KexiQueryDesignerGuiEditor::KexiQueryDesignerGuiEditor(
00142     KexiMainWindow *mainWin, QWidget *parent, const char *name)
00143  : KexiViewBase(mainWin, parent, name)
00144  , d( new Private() )
00145 {
00146     d->conn = mainWin->project()->dbConnection();
00147 
00148     d->spl = new QSplitter(Vertical, this);
00149     d->spl->setChildrenCollapsible(false);
00150     d->relations = new KexiRelationWidget(mainWin, d->spl, "relations");
00151     connect(d->relations, SIGNAL(tableAdded(KexiDB::TableSchema&)),
00152         this, SLOT(slotTableAdded(KexiDB::TableSchema&)));
00153     connect(d->relations, SIGNAL(tableHidden(KexiDB::TableSchema&)),
00154         this, SLOT(slotTableHidden(KexiDB::TableSchema&)));
00155     connect(d->relations, SIGNAL(tableFieldDoubleClicked(KexiDB::TableSchema*,const QString&)),
00156         this, SLOT(slotTableFieldDoubleClicked(KexiDB::TableSchema*,const QString&)));
00157 
00158     d->head = new KexiSectionHeader(i18n("Query Columns"), Vertical, d->spl);
00159     d->dataTable = new KexiDataTable(mainWin, d->head, "guieditor_dataTable", false);
00160     d->dataTable->dataAwareObject()->setSpreadSheetMode();
00161 
00162     d->data = new KexiTableViewData(); //just empty data
00163     d->sets = new KexiDataAwarePropertySet( this, d->dataTable->dataAwareObject() );
00164     initTableColumns();
00165     initTableRows();
00166 
00167     QValueList<int> c;
00168     c << COLUMN_ID_COLUMN << COLUMN_ID_TABLE << COLUMN_ID_CRITERIA;
00169     if (d->dataTable->tableView()/*sanity*/) {
00170         d->dataTable->tableView()->adjustColumnWidthToContents(COLUMN_ID_VISIBLE);
00171         d->dataTable->tableView()->adjustColumnWidthToContents(COLUMN_ID_SORTING);
00172         d->dataTable->tableView()->maximizeColumnsWidth( c );
00173         d->dataTable->tableView()->setDropsAtRowEnabled(true);
00174         connect(d->dataTable->tableView(), SIGNAL(dragOverRow(KexiTableItem*,int,QDragMoveEvent*)),
00175             this, SLOT(slotDragOverTableRow(KexiTableItem*,int,QDragMoveEvent*)));
00176         connect(d->dataTable->tableView(), SIGNAL(droppedAtRow(KexiTableItem*,int,QDropEvent*,KexiTableItem*&)),
00177             this, SLOT(slotDroppedAtRow(KexiTableItem*,int,QDropEvent*,KexiTableItem*&)));
00178         connect(d->dataTable->tableView(), SIGNAL(newItemAppendedForAfterDeletingInSpreadSheetMode()),
00179             this, SLOT(slotNewItemAppendedForAfterDeletingInSpreadSheetMode()));
00180     }
00181     connect(d->data, SIGNAL(aboutToChangeCell(KexiTableItem*,int,QVariant&,KexiDB::ResultInfo*)),
00182         this, SLOT(slotBeforeCellChanged(KexiTableItem*,int,QVariant&,KexiDB::ResultInfo*)));
00183     connect(d->data, SIGNAL(rowInserted(KexiTableItem*,uint,bool)),
00184         this, SLOT(slotRowInserted(KexiTableItem*,uint,bool)));
00185     connect(d->relations, SIGNAL(tablePositionChanged(KexiRelationViewTableContainer*)),
00186         this, SLOT(slotTablePositionChanged(KexiRelationViewTableContainer*)));
00187     connect(d->relations, SIGNAL(aboutConnectionRemove(KexiRelationViewConnection*)),
00188         this, SLOT(slotAboutConnectionRemove(KexiRelationViewConnection*)));
00189 
00190     QVBoxLayout *l = new QVBoxLayout(this);
00191     l->addWidget(d->spl);
00192 
00193     addChildView(d->relations);
00194     addChildView(d->dataTable);
00195     setViewWidget(d->dataTable, true);
00196     d->relations->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);
00197     d->head->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);
00198     updateGeometry();
00199     d->spl->setSizes(QValueList<int>()<< 800<<400);
00200 }
00201 
00202 KexiQueryDesignerGuiEditor::~KexiQueryDesignerGuiEditor()
00203 {
00204 }
00205 
00206 void
00207 KexiQueryDesignerGuiEditor::initTableColumns()
00208 {
00209     KexiTableViewColumn *col1 = new KexiTableViewColumn("column", KexiDB::Field::Enum, i18n("Column"),
00210         i18n("Describes field name or expression for the designed query."));
00211     col1->setRelatedDataEditable(true);
00212 
00213     d->fieldColumnData = new KexiTableViewData(KexiDB::Field::Text, KexiDB::Field::Text);
00214     col1->setRelatedData( d->fieldColumnData );
00215     d->data->addColumn(col1);
00216 
00217     KexiTableViewColumn *col2 = new KexiTableViewColumn("table", KexiDB::Field::Enum, i18n("Table"),
00218         i18n("Describes table for a given field. Can be empty."));
00219     d->tablesColumnData = new KexiTableViewData(KexiDB::Field::Text, KexiDB::Field::Text);
00220     col2->setRelatedData( d->tablesColumnData );
00221     d->data->addColumn(col2);
00222 
00223     KexiTableViewColumn *col3 = new KexiTableViewColumn("visible", KexiDB::Field::Boolean, i18n("Visible"),
00224         i18n("Describes visibility for a given field or expression."));
00225     col3->field()->setDefaultValue( QVariant(false, 0) );
00226     col3->field()->setNotNull( true );
00227     d->data->addColumn(col3);
00228 
00229 #ifndef KEXI_NO_QUERY_TOTALS
00230     KexiTableViewColumn *col4 = new KexiTableViewColumn("totals", KexiDB::Field::Enum, i18n("Totals"),
00231         i18n("Describes a way of computing totals for a given field or expression."));
00232     QValueVector<QString> totalsTypes;
00233     totalsTypes.append( i18n("Group by") );
00234     totalsTypes.append( i18n("Sum") );
00235     totalsTypes.append( i18n("Average") );
00236     totalsTypes.append( i18n("Min") );
00237     totalsTypes.append( i18n("Max") );
00238     //todo: more like this
00239     col4->field()->setEnumHints(totalsTypes);
00240     d->data->addColumn(col4);
00241 #endif
00242 
00243     KexiTableViewColumn *col5 = new KexiTableViewColumn("sort", KexiDB::Field::Enum, i18n("Sorting"),
00244         i18n("Describes a way of sorting for a given field."));
00245     QValueVector<QString> sortTypes;
00246     sortTypes.append( "" );
00247     sortTypes.append( i18n("Ascending") );
00248     sortTypes.append( i18n("Descending") );
00249     col5->field()->setEnumHints(sortTypes);
00250     d->data->addColumn(col5);
00251 
00252     KexiTableViewColumn *col6 = new KexiTableViewColumn("criteria", KexiDB::Field::Text, i18n("Criteria"),
00253         i18n("Describes the criteria for a given field or expression."));
00254     d->data->addColumn(col6);
00255 
00256 //  KexiTableViewColumn *col7 = new KexiTableViewColumn(i18n("Or"), KexiDB::Field::Text);
00257 //  d->data->addColumn(col7);
00258 }
00259 
00260 void KexiQueryDesignerGuiEditor::initTableRows()
00261 {
00262     d->data->deleteAllRows();
00263     //const int columns = d->data->columnsCount();
00264     for (int i=0; i<(int)d->sets->size(); i++) {
00265         KexiTableItem* item;
00266         d->data->append(item = d->data->createItem());
00267         item->at(COLUMN_ID_VISIBLE) = QVariant(false, 0);
00268     }
00269     d->dataTable->dataAwareObject()->setData(d->data);
00270 
00271     updateColumnsData();
00272 }
00273 
00274 void KexiQueryDesignerGuiEditor::updateColumnsData()
00275 {
00276     d->dataTable->dataAwareObject()->acceptRowEdit();
00277 
00278     QStringList sortedTableNames;
00279     for (TablesDictIterator it(*d->relations->tables());it.current();++it)
00280         sortedTableNames += it.current()->schema()->name();
00281     qHeapSort( sortedTableNames );
00282 
00283     //several tables can be hidden now, so remove rows for these tables
00284     QValueList<int> rowsToDelete;
00285     for (int r = 0; r<(int)d->sets->size(); r++) {
00286         KoProperty::Set *set = d->sets->at(r);
00287         if (set) {
00288             QString tableName = (*set)["table"].value().toString();
00289             QString fieldName = (*set)["field"].value().toString();
00290             const bool allTablesAsterisk = tableName=="*" && d->relations->tables()->isEmpty();
00291             const bool fieldNotFound = tableName!="*"
00292                 && !(*set)["isExpression"].value().toBool()
00293                 && sortedTableNames.end() == qFind( sortedTableNames.begin(), sortedTableNames.end(), tableName );
00294 
00295             if (allTablesAsterisk || fieldNotFound) {
00296                 //table not found: mark this line for later removal
00297                 rowsToDelete += r;
00298             }
00299         }
00300     }
00301     d->data->deleteRows( rowsToDelete );
00302 
00303     //update 'table' and 'field' columns
00304     d->tablesColumnData->deleteAllRows();
00305     d->fieldColumnData->deleteAllRows();
00306     d->fieldColumnIdentifiers.clear();
00307 
00308     KexiTableItem *item = d->fieldColumnData->createItem(); //new KexiTableItem(2);
00309     (*item)[COLUMN_ID_COLUMN]="*";
00310     (*item)[COLUMN_ID_TABLE]="*";
00311     d->fieldColumnData->append( item );
00312     d->fieldColumnIdentifiers.insert((*item)[COLUMN_ID_COLUMN].toString(), (char*)1); //cache
00313 
00314 //  tempData()->clearQuery();
00315     tempData()->unregisterForTablesSchemaChanges();
00316     for (QStringList::const_iterator it = sortedTableNames.constBegin();
00317         it!=sortedTableNames.constEnd(); ++it)
00318     {
00319         //table
00321         KexiDB::TableSchema *table = d->relations->tables()->find(*it)->schema()->table();
00322         d->conn->registerForTableSchemaChanges(*tempData(), *table); //this table will be used
00323         item = d->tablesColumnData->createItem(); //new KexiTableItem(2);
00324         (*item)[COLUMN_ID_COLUMN]=table->name();
00325         (*item)[COLUMN_ID_TABLE]=(*item)[COLUMN_ID_COLUMN];
00326         d->tablesColumnData->append( item );
00327         //fields
00328         item = d->fieldColumnData->createItem(); //new KexiTableItem(2);
00329         (*item)[COLUMN_ID_COLUMN]=table->name()+".*";
00330         (*item)[COLUMN_ID_TABLE]=(*item)[COLUMN_ID_COLUMN];
00331         d->fieldColumnData->append( item );
00332         d->fieldColumnIdentifiers.insert((*item)[COLUMN_ID_COLUMN].toString(), (char*)1); //cache
00333         for (KexiDB::Field::ListIterator t_it = table->fieldsIterator();t_it.current();++t_it) {
00334             item = d->fieldColumnData->createItem(); // new KexiTableItem(2);
00335             (*item)[COLUMN_ID_COLUMN]=table->name()+"."+t_it.current()->name();
00336             (*item)[COLUMN_ID_TABLE]=QString("  ") + t_it.current()->name();
00337             d->fieldColumnData->append( item );
00338             d->fieldColumnIdentifiers.insert((*item)[COLUMN_ID_COLUMN].toString(), (char*)1); //cache
00339         }
00340     }
00341 //TODO
00342 }
00343 
00344 KexiRelationWidget *KexiQueryDesignerGuiEditor::relationView() const
00345 {
00346     return d->relations;
00347 }
00348 
00349 KexiQueryPart::TempData *
00350 KexiQueryDesignerGuiEditor::tempData() const
00351 {
00352     return static_cast<KexiQueryPart::TempData*>(parentDialog()->tempData());
00353 }
00354 
00355 static QString msgCannotSwitch_EmptyDesign() {
00356     return i18n("Cannot switch to data view, because query design is empty.\n"
00357         "First, please create your design.");
00358 }
00359 
00360 bool
00361 KexiQueryDesignerGuiEditor::buildSchema(QString *errMsg)
00362 {
00363     //build query schema
00364     KexiQueryPart::TempData * temp = tempData();
00365     if (temp->query()) {
00366         temp->clearQuery();
00367     } else {
00368         temp->setQuery( new KexiDB::QuerySchema() );
00369     }
00370 
00371     //add tables
00372     for (TablesDictIterator it(*d->relations->tables()); it.current(); ++it) {
00374         temp->query()->addTable( it.current()->schema()->table() );
00375     }
00376 
00377     //add fields, also build:
00378     // -WHERE expression
00379     // -ORDER BY list
00380     KexiDB::BaseExpr *whereExpr = 0;
00381     const uint count = QMIN(d->data->count(), d->sets->size());
00382     bool fieldsFound = false;
00383     KexiTableViewData::Iterator it(d->data->iterator());
00384     for (uint i=0; i<count && it.current(); ++it, i++) {
00385         if (!it.current()->at(COLUMN_ID_TABLE).isNull() && it.current()->at(COLUMN_ID_COLUMN).isNull()) {
00386             //show message about missing field name, and set focus to that cell
00387             kexipluginsdbg << "no field provided!" << endl;
00388             d->dataTable->dataAwareObject()->setCursorPosition(i,0);
00389             if (errMsg)
00390                 *errMsg = i18n("Select column for table \"%1\"")
00391                     .arg(it.current()->at(COLUMN_ID_TABLE).toString());
00392             return false;
00393         }
00394 
00395         KoProperty::Set *set = d->sets->at(i);
00396         if (set) {
00397             QString tableName = (*set)["table"].value().toString().stripWhiteSpace();
00398             QString fieldName = (*set)["field"].value().toString();
00399             QString fieldAndTableName = fieldName;
00400             KexiDB::Field *currentField = 0; // will be set if this column is a single field
00401             KexiDB::QueryColumnInfo* currentColumn = 0;
00402             if (!tableName.isEmpty())
00403                 fieldAndTableName.prepend(tableName+".");
00404             const bool fieldVisible = (*set)["visible"].value().toBool();
00405             QString criteriaStr = (*set)["criteria"].value().toString();
00406             QCString alias = (*set)["alias"].value().toCString();
00407             if (!criteriaStr.isEmpty()) {
00408                 int token;
00409                 KexiDB::BaseExpr *criteriaExpr = parseExpressionString(criteriaStr, token, 
00410                     true/*allowRelationalOperator*/);
00411                 if (!criteriaExpr) {//for sanity
00412                     if (errMsg)
00413                         *errMsg = i18n("Invalid criteria \"%1\"").arg(criteriaStr);
00414                     delete whereExpr;
00415                     return false;
00416                 }
00417                 //build relational expression for column variable
00418                 KexiDB::VariableExpr *varExpr = new KexiDB::VariableExpr(fieldAndTableName);
00419                 criteriaExpr = new KexiDB::BinaryExpr(KexiDBExpr_Relational, varExpr, token, criteriaExpr);
00420                 //critera ok: add it to WHERE section
00421                 if (whereExpr)
00422                     whereExpr = new KexiDB::BinaryExpr(KexiDBExpr_Logical, whereExpr, AND, criteriaExpr);
00423                 else //first expr.
00424                     whereExpr = criteriaExpr;
00425             }
00426             if (tableName.isEmpty()) {
00427                 if ((*set)["isExpression"].value().toBool()==true) {
00428                     //add expression column
00429                     int dummyToken;
00430                     KexiDB::BaseExpr *columnExpr = parseExpressionString(fieldName, dummyToken, 
00431                         false);
00432                     if (!columnExpr) {
00433                         if (errMsg)
00434                             *errMsg = i18n("Invalid expression \"%1\"").arg(fieldName);
00435                         return false;
00436                     }
00437                     temp->query()->addExpression(columnExpr, fieldVisible);
00438                     if (fieldVisible)
00439                         fieldsFound = true;
00440                     if (!alias.isEmpty())
00441                         temp->query()->setColumnAlias( temp->query()->fieldCount()-1, alias );
00442                 }
00443                 //TODO
00444             }
00445             else if (tableName=="*") {
00446                 //all tables asterisk
00447                 temp->query()->addAsterisk( new KexiDB::QueryAsterisk( temp->query(), 0 ), fieldVisible );
00448                 if (fieldVisible)
00449                     fieldsFound = true;
00450                 continue;
00451             }
00452             else {
00453                 KexiDB::TableSchema *t = d->conn->tableSchema(tableName);
00454                 if (fieldName=="*") {
00455                     //single-table asterisk: <tablename> + ".*" + number
00456                     temp->query()->addAsterisk( new KexiDB::QueryAsterisk( temp->query(), t ), fieldVisible );
00457                     if (fieldVisible)
00458                         fieldsFound = true;
00459                 } else {
00460                     if (!t) {
00461                         kexipluginswarn << "query designer: NO TABLE '" 
00462                             << (*set)["table"].value().toString() << "'" << endl;
00463                         continue;
00464                     }
00465                     currentField = t->field( fieldName );
00466                     if (!currentField) {
00467                         kexipluginswarn << "query designer: NO FIELD '" << fieldName << "'" << endl;
00468                         continue;
00469                     }
00470                     if (!fieldVisible && criteriaStr.isEmpty() && (*set)["isExpression"]
00471                         && (*set)["sorting"].value().toString()!="nosorting")
00472                     {
00473                         kexipluginsdbg << "invisible field with sorting: do not add it to the fields list" << endl;
00474                         continue;
00475                     }
00476                     temp->query()->addField(currentField, fieldVisible);
00477                     currentColumn = temp->query()->expandedOrInternalField( 
00478                         temp->query()->fieldsExpanded().count() - 1 );
00479                     if (fieldVisible)
00480                         fieldsFound = true;
00481                     if (!alias.isEmpty())
00482                         temp->query()->setColumnAlias( temp->query()->fieldCount()-1, alias );
00483                 }
00484             }
00485         }
00486         else {
00487             kexipluginsdbg << it.current()->at(COLUMN_ID_TABLE).toString() << endl;
00488         }
00489     }
00490     if (!fieldsFound) {
00491         if (errMsg)
00492             *errMsg = msgCannotSwitch_EmptyDesign();
00493         return false;
00494     }
00495     if (whereExpr)
00496         kexipluginsdbg << "KexiQueryDesignerGuiEditor::buildSchema(): setting CRITERIA: " 
00497             << whereExpr->debugString() << endl;
00498 
00499     //set always, because if whereExpr==NULL,
00500     //this will clear prev. expr
00501     temp->query()->setWhereExpression( whereExpr );
00502 
00503     //add relations (looking for connections)
00504     for (ConnectionListIterator it(*d->relations->connections()); it.current(); ++it) {
00505         KexiRelationViewTableContainer *masterTable = it.current()->masterTable();
00506         KexiRelationViewTableContainer *detailsTable = it.current()->detailsTable();
00507 
00509         temp->query()->addRelationship(
00510             masterTable->schema()->table()->field(it.current()->masterField()),
00511             detailsTable->schema()->table()->field(it.current()->detailsField()) );
00512     }
00513 
00514     // Add sorting information (ORDER BY) - we can do that only now
00515     //  after all QueryColumnInfo items are instantiated
00516     KexiDB::OrderByColumnList orderByColumns;
00517     it = d->data->iterator();
00518     int fieldNumber = -1; //field number (empty rows are omitted)
00519     for (uint i=0/*row number*/; i<count && it.current(); ++it, i++) {
00520         KoProperty::Set *set = d->sets->at(i);
00521         if (!set)
00522             continue;
00523         fieldNumber++;
00524         KexiDB::Field *currentField = 0;
00525         KexiDB::QueryColumnInfo *currentColumn = 0;
00526         QString sortingString( (*set)["sorting"].value().toString() );
00527         if (sortingString!="ascending" && sortingString!="descending")
00528             continue;
00529         if (!(*set)["visible"].value().toBool()) {
00530             // this row defines invisible field but contains sorting information,
00531             // what means KexiDB::Field should be used as a reference for this sorting
00532             // Note1: alias is not supported here.
00533 
00534             // Try to find a field (not mentioned after SELECT):
00535             currentField = temp->query()->findTableField( (*set)["field"].value().toString() );
00536             if (!currentField) {
00537                 kexipluginswarn << "KexiQueryDesignerGuiEditor::buildSchema(): NO FIELD '" 
00538                     << (*set)["field"].value().toString()
00539                     << " available for sorting" << endl;
00540                 continue;
00541             }
00542             orderByColumns.appendField(*currentField, sortingString=="ascending");
00543             continue;
00544         }
00545         currentField = temp->query()->field( (uint)fieldNumber );
00546         if (!currentField || currentField->isExpression() || currentField->isQueryAsterisk())
00548             continue;
00550         QString aliasString( (*set)["alias"].value().toString() );
00551         currentColumn = temp->query()->columnInfo( 
00552             (*set)["table"].value().toString() + "."
00553             + (aliasString.isEmpty() ? currentField->name() : aliasString) );
00554         if (currentField && currentColumn) {
00555             if (currentColumn->visible)
00556                 orderByColumns.appendColumn(*currentColumn, sortingString=="ascending");
00557             else if (currentColumn->field)
00558                 orderByColumns.appendField(*currentColumn->field, sortingString=="ascending");
00559         }
00560     }
00561     temp->query()->setOrderByColumnList( orderByColumns );
00562 
00563     temp->query()->debug();
00564     temp->registerTableSchemaChanges(temp->query());
00565     //TODO?
00566     return true;
00567 }
00568 
00569 tristate
00570 KexiQueryDesignerGuiEditor::beforeSwitchTo(int mode, bool &dontStore)
00571 {
00572     kexipluginsdbg << "KexiQueryDesignerGuiEditor::beforeSwitch()" << mode << endl;
00573 
00574     if (!d->dataTable->dataAwareObject()->acceptRowEdit())
00575         return cancelled;
00576 
00577     if (mode==Kexi::DesignViewMode) {
00578         return true;
00579     }
00580     else if (mode==Kexi::DataViewMode) {
00581 //      if (!d->dataTable->dataAwareObject()->acceptRowEdit())
00582     //      return cancelled;
00583 
00584         if (!dirty() && parentDialog()->neverSaved()) {
00585             KMessageBox::information(this, msgCannotSwitch_EmptyDesign());
00586             return cancelled;
00587         }
00588         if (dirty() || !tempData()->query()) {
00589             //remember current design in a temporary structure
00590             dontStore=true;
00591             QString errMsg;
00592             //build schema; problems are not allowed
00593             if (!buildSchema(&errMsg)) {
00594                 KMessageBox::sorry(this, errMsg);
00595                 return cancelled;
00596             }
00597         }
00598         //TODO
00599         return true;
00600     }
00601     else if (mode==Kexi::TextViewMode) {
00602         dontStore=true;
00603         //build schema; ignore problems
00604         buildSchema();
00605 /*      if (tempData()->query && tempData()->query->fieldCount()==0) {
00606             //no fields selected: let's add "*" (all-tables asterisk),
00607             // otherwise SQL statement will be invalid
00608             tempData()->query->addAsterisk( new KexiDB::QueryAsterisk( tempData()->query ) );
00609         }*/
00610         //todo
00611         return true;
00612     }
00613 
00614     return false;
00615 }
00616 
00617 tristate
00618 KexiQueryDesignerGuiEditor::afterSwitchFrom(int mode)
00619 {
00620     const bool was_dirty = dirty();
00621     KexiDB::Connection *conn = parentDialog()->mainWin()->project()->dbConnection();
00622     if (mode==Kexi::NoViewMode || (mode==Kexi::DataViewMode && !tempData()->query())) {
00623         //this is not a SWITCH but a fresh opening in this view mode
00624         if (!m_dialog->neverSaved()) {
00625             if (!loadLayout()) {
00626                 //err msg
00627                 parentDialog()->setStatus(conn,
00628                     i18n("Query definition loading failed."),
00629                     i18n("Query design may be corrupted so it could not be opened even in text view.\n"
00630                         "You can delete the query and create it again."));
00631                 return false;
00632             }
00633             // Invalid queries case:
00634             // KexiDialogBase::switchToViewMode() first opens DesignViewMode,
00635             // and then KexiQueryPart::loadSchemaData() doesn't allocate QuerySchema object
00636             // do we're carefully looking at parentDialog()->schemaData()
00637             KexiDB::QuerySchema * q = dynamic_cast<KexiDB::QuerySchema *>(parentDialog()->schemaData());
00638             if (q) {
00639                 KexiDB::ResultInfo result;
00640                 showFieldsForQuery( q, result );
00641                 if (!result.success) {
00642                     parentDialog()->setStatus(&result, i18n("Query definition loading failed."));
00643                     tempData()->proposeOpeningInTextViewModeBecauseOfProblems = true;
00644                     return false;
00645                 }
00646             }
00648         }
00649     }
00650     else if (mode==Kexi::TextViewMode || mode==Kexi::DataViewMode) {
00651         // Switch from text or data view. In the second case, the design could be changed as well
00652         // because there could be changes made in the text view before switching to the data view.
00653         if (tempData()->queryChangedInPreviousView) {
00654             //previous view changed query data
00655             //-clear and regenerate GUI items
00656             initTableRows();
00657             //todo
00658             if (tempData()->query()) {
00659                 //there is a query schema to show
00660                 showTablesForQuery( tempData()->query() );
00661                 //-show fields
00662                 KexiDB::ResultInfo result;
00663                 showFieldsAndRelationsForQuery( tempData()->query(), result );
00664                 if (!result.success) {
00665                     parentDialog()->setStatus(&result, i18n("Query definition loading failed."));
00666                     return false;
00667                 }
00668             }
00669             else {
00670                 d->relations->clear();
00671             }
00672         }
00674     }
00675     
00676     if (mode==Kexi::DataViewMode) {
00677         //this is just a SWITCH from data view
00678         //set cursor if needed:
00679         if (d->dataTable->dataAwareObject()->currentRow()<0
00680             || d->dataTable->dataAwareObject()->currentColumn()<0)
00681         {
00682             d->dataTable->dataAwareObject()->ensureCellVisible(0,0);
00683             d->dataTable->dataAwareObject()->setCursorPosition(0,0);
00684         }
00685     }
00686     tempData()->queryChangedInPreviousView = false;
00687     setFocus(); //to allow shared actions proper update
00688     if (!was_dirty)
00689         setDirty(false);
00690     return true;
00691 }
00692 
00693 
00694 KexiDB::SchemaData*
00695 KexiQueryDesignerGuiEditor::storeNewData(const KexiDB::SchemaData& sdata, bool &cancel)
00696 {
00697     if (!d->dataTable->dataAwareObject()->acceptRowEdit()) {
00698         cancel = true;
00699         return 0;
00700     }
00701     QString errMsg;
00702     KexiQueryPart::TempData * temp = tempData();
00703     if (!temp->query() || !(viewMode()==Kexi::DesignViewMode && !temp->queryChangedInPreviousView)) {
00704         //only rebuild schema if it has not been rebuilt previously
00705         if (!buildSchema(&errMsg)) {
00706             KMessageBox::sorry(this, errMsg);
00707             cancel = true;
00708             return 0;
00709         }
00710     }
00711     (KexiDB::SchemaData&)*temp->query() = sdata; //copy main attributes
00712 
00713     bool ok = m_mainWin->project()->dbConnection()->storeObjectSchemaData( *temp->query(), true /*newObject*/ );
00714     m_dialog->setId( temp->query()->id() );
00715 
00716     if (ok)
00717         ok = storeLayout();
00718 
00719 //  temp->query = 0; //will be returned, so: don't keep it
00720     if (!ok) {
00721         temp->setQuery( 0 );
00722 //      delete query;
00723         return 0;
00724     }
00725     return temp->takeQuery(); //will be returned, so: don't keep it in temp
00726 }
00727 
00728 tristate KexiQueryDesignerGuiEditor::storeData(bool dontAsk)
00729 {
00730     if (!d->dataTable->dataAwareObject()->acceptRowEdit())
00731         return cancelled;
00732 
00733     const bool was_dirty = dirty();
00734     tristate res = KexiViewBase::storeData(dontAsk); //this clears dirty flag
00735     if (true == res)
00736         res = buildSchema();
00737     if (true == res)
00738         res = storeLayout();
00739     if (true != res) {
00740         if (was_dirty)
00741             setDirty(true);
00742     }
00743     return res;
00744 }
00745 
00746 void KexiQueryDesignerGuiEditor::showTablesForQuery(KexiDB::QuerySchema *query)
00747 {
00748 //replaced by code below that preserves geometries d->relations->clear();
00749 
00750     // instead of hiding all tables and showing some tables,
00751     // show only these new and hide these unncecessary; the same for connections)
00752     d->slotTableAdded_enabled = false; //speedup
00753     d->relations->removeAllConnections(); //connections will be recreated
00754     d->relations->hideAllTablesExcept( query->tables() );
00755     for (KexiDB::TableSchema::ListIterator it(*query->tables()); it.current(); ++it) {
00756         d->relations->addTable( it.current() );
00757     }
00758 
00759     d->slotTableAdded_enabled = true;
00760     updateColumnsData();
00761 }
00762 
00763 void KexiQueryDesignerGuiEditor::addConnection(
00764     KexiDB::Field *masterField, KexiDB::Field *detailsField)
00765 {
00766     SourceConnection conn;
00767     conn.masterTable = masterField->table()->name(); //<<<TODO
00768     conn.masterField = masterField->name();
00769     conn.detailsTable = detailsField->table()->name();
00770     conn.detailsField = detailsField->name();
00771     d->relations->addConnection( conn );
00772 }
00773 
00774 void KexiQueryDesignerGuiEditor::showFieldsForQuery(KexiDB::QuerySchema *query, KexiDB::ResultInfo& result)
00775 {
00776     showFieldsOrRelationsForQueryInternal(query, true, false, result);
00777 }
00778 
00779 void KexiQueryDesignerGuiEditor::showRelationsForQuery(KexiDB::QuerySchema *query, KexiDB::ResultInfo& result)
00780 {
00781     showFieldsOrRelationsForQueryInternal(query, false, true, result);
00782 }
00783 
00784 void KexiQueryDesignerGuiEditor::showFieldsAndRelationsForQuery(KexiDB::QuerySchema *query, 
00785     KexiDB::ResultInfo& result)
00786 {
00787     showFieldsOrRelationsForQueryInternal(query, true, true, result);
00788 }
00789 
00790 void KexiQueryDesignerGuiEditor::showFieldsOrRelationsForQueryInternal(
00791     KexiDB::QuerySchema *query, bool showFields, bool showRelations, KexiDB::ResultInfo& result)
00792 {
00793     result.clear();
00794     const bool was_dirty = dirty();
00795 
00796     //1. Show explicitly declared relations:
00797     if (showRelations) {
00798         KexiDB::Relationship *rel;
00799         for (KexiDB::Relationship::ListIterator it(*query->relationships());
00800             (rel=it.current()); ++it)
00801         {
00803             KexiDB::Field *masterField = rel->masterIndex()->fields()->first();
00804             KexiDB::Field *detailsField = rel->detailsIndex()->fields()->first();
00805             addConnection(masterField, detailsField);
00806         }
00807     }
00808 
00809     //2. Collect information about criterias
00810     // --this must be top level chain of AND's
00811     // --this will also show joins as: [table1.]field1 = [table2.]field2
00812     QDict<KexiDB::BaseExpr> criterias(101, false);
00813     KexiDB::BaseExpr* e = query->whereExpression();
00814     KexiDB::BaseExpr* eItem = 0;
00815     while (e) {
00816         //eat parentheses because the expression can be (....) AND (... AND ... )
00817         while (e && e->toUnary() && e->token()=='(')
00818             e = e->toUnary()->arg();
00819 
00820         if (e->toBinary() && e->token()==AND) {
00821             eItem = e->toBinary()->left();
00822             e = e->toBinary()->right();
00823         }
00824         else {
00825             eItem = e;
00826             e = 0;
00827         }
00828 
00829         //eat parentheses
00830         while (eItem && eItem->toUnary() && eItem->token()=='(')
00831             eItem = eItem->toUnary()->arg();
00832 
00833         if (!eItem)
00834             continue;
00835 
00836         kexidbg << eItem->toString() << endl;
00837         KexiDB::BinaryExpr* binary = eItem->toBinary();
00838         if (binary && eItem->exprClass()==KexiDBExpr_Relational) {
00839             KexiDB::Field *leftField = 0, *rightField = 0;
00840             if (eItem->token()=='='
00841                 && binary->left()->toVariable()
00842                 && binary->right()->toVariable()
00843                 && (leftField = query->findTableField( binary->left()->toString() ))
00844                 && (rightField = query->findTableField( binary->right()->toString() )))
00845             {
00849 
00850                 //this is relationship defined as following JOIN: [table1.]field1 = [table2.]field2
00851                 if (showRelations) {
00854                     if (leftField->isPrimaryKey())
00855                         addConnection(leftField /*master*/, rightField /*details*/);
00856                     else
00857                         addConnection(rightField /*master*/, leftField /*details*/);
00859                 }
00860             }
00861             else if (binary->left()->toVariable()) {
00862                 //this is: variable , op , argument
00863                 //store variable -> argument:
00864                 criterias.insert(binary->left()->toVariable()->name, binary->right());
00865             }
00866             else if (binary->right()->toVariable()) {
00867                 //this is: argument , op , variable
00868                 //store variable -> argument:
00869                 criterias.insert(binary->right()->toVariable()->name, binary->left());
00870             }
00871         }
00872     } //while
00873 
00874     if (!showFields)
00875         return;
00876 
00877     //3. show fields (including * and table.*)
00878     uint row_num = 0;
00879     KexiDB::Field *field;
00880     QPtrDict<char> usedCriterias(101); // <-- used criterias will be saved here
00881                                        //     so in step 4. we will be able to add
00882                                        //     remaining invisible columns with criterias
00883     for (KexiDB::Field::ListIterator it(*query->fields());
00884         (field = it.current()); ++it, row_num++)
00885     {
00886         //append a new row
00887         QString tableName, fieldName, columnAlias, criteriaString;
00888         KexiDB::BinaryExpr *criteriaExpr = 0;
00889         KexiDB::BaseExpr *criteriaArgument = 0;
00890         if (field->isQueryAsterisk()) {
00891             if (field->table()) {//single-table asterisk
00892                 tableName = field->table()->name();
00893                 fieldName = "*";
00894             }
00895             else {//all-tables asterisk
00896                 tableName = "*";
00897                 fieldName = "";
00898             }
00899         }
00900         else {
00901             columnAlias = query->columnAlias(row_num);
00902             if (field->isExpression()) {
00903 //              if (columnAlias.isEmpty()) {
00904 //                  columnAlias = i18n("expression", "expr%1").arg(row_num); //TODO
00905 //              }
00906 //              if (columnAlias.isEmpty())
00907 //TODO: ok? perhaps do not allow to omit aliases?
00908                     fieldName = field->expression()->toString();
00909 //              else
00910 //                  fieldName = columnAlias + ": " + field->expression()->toString();
00911             }
00912             else {
00913                 tableName = field->table()->name();
00914                 fieldName = field->name();
00915                 criteriaArgument = criterias[fieldName];
00916                 if (!criteriaArgument) {//try table.field
00917                     criteriaArgument = criterias[tableName+"."+fieldName];
00918                 }
00919                 if (criteriaArgument) {//criteria expression is just a parent of argument
00920                     criteriaExpr = criteriaArgument->parent()->toBinary();
00921                     usedCriterias.insert(criteriaArgument, (char*)1); //save info. about used criteria
00922                 }
00923             }
00924         }
00925         //create new row data
00926         KexiTableItem *newItem = createNewRow(tableName, fieldName, true /* visible*/);
00927         if (criteriaExpr) {
00929             if (criteriaExpr->token()=='=')
00930                 criteriaString = criteriaArgument->toString();
00931             else
00932                 criteriaString = criteriaExpr->tokenToString() + " " + criteriaArgument->toString();
00933             (*newItem)[COLUMN_ID_CRITERIA] = criteriaString;
00934         }
00935         d->dataTable->dataAwareObject()->insertItem(newItem, row_num);
00936         //OK, row inserted: create a new set for it
00937         KoProperty::Set &set = *createPropertySet( row_num, tableName, fieldName, true/*new one*/ );
00938         if (!columnAlias.isEmpty())
00939             set["alias"].setValue(columnAlias, false);
00940         if (!criteriaString.isEmpty())
00941             set["criteria"].setValue( criteriaString, false );
00942         if (field->isExpression()) {
00943 //          (*newItem)[COLUMN_ID_COLUMN] = ;
00944             if (!d->changeSingleCellValue(*newItem, COLUMN_ID_COLUMN, 
00945                     QVariant(columnAlias + ": " + field->expression()->toString()), &result))
00946                 return; //problems with setting column expression
00947         }
00948     }
00949 
00950     //4. show ORDER BY information
00951     d->data->clearRowEditBuffer();
00952     KexiDB::OrderByColumnList &orderByColumns = query->orderByColumnList();
00953     QMap<KexiDB::QueryColumnInfo*,int> columnsOrder( 
00954         query->columnsOrder(KexiDB::QuerySchema::UnexpandedListWithoutAsterisks) );
00955     for (KexiDB::OrderByColumn::ListConstIterator orderByColumnsIt( orderByColumns.constBegin() );
00956         orderByColumnsIt!=orderByColumns.constEnd(); ++orderByColumnsIt)
00957     {
00958         KexiDB::QueryColumnInfo *column = (*orderByColumnsIt).column();
00959         KexiTableItem *rowItem = 0;
00960         KoProperty::Set *rowPropertySet = 0;
00961         if (column) {
00962             //sorting for visible column
00963             if (column->visible) {
00964                 if (columnsOrder.contains(column)) {
00965                     const int columnPosition = columnsOrder[ column ];
00966                     rowItem = d->data->at( columnPosition );
00967                     rowPropertySet = d->sets->at( columnPosition );
00968                     kexipluginsdbg << "KexiQueryDesignerGuiEditor::showFieldsOrRelationsForQueryInternal():\n\t"
00969                         "Setting \"" << (*orderByColumnsIt).debugString() << "\" sorting for row #" 
00970                         << columnPosition << endl;
00971                 }
00972             }
00973         }
00974         else if ((*orderByColumnsIt).field()) {
00975             //this will be presented as invisible field: create new row
00976             field = (*orderByColumnsIt).field();
00977             QString tableName( field->table() ? field->table()->name() : QString::null );
00978             rowItem = createNewRow( tableName, field->name(), false /* !visible*/);
00979             d->dataTable->dataAwareObject()->insertItem(rowItem, row_num);
00980             rowPropertySet = createPropertySet( row_num, tableName, field->name(), true /*newOne*/ );
00981             propertySetSwitched();
00982             kexipluginsdbg << "KexiQueryDesignerGuiEditor::showFieldsOrRelationsForQueryInternal():\n\t"
00983                 "Setting \"" << (*orderByColumnsIt).debugString() << "\" sorting for invisible field " 
00984                 << field->name() << ", table " << tableName << " -row #" << row_num << endl;
00985             row_num++;
00986         }
00987         //alter sorting for either existing or new row
00988         if (rowItem && rowPropertySet) {
00989             d->data->updateRowEditBuffer(rowItem, COLUMN_ID_SORTING,
00990                 (*orderByColumnsIt).ascending() ? 1 : 2); // this will automatically update "sorting" property
00991                                                                 // in slotBeforeCellChanged()
00992             d->data->saveRowChanges(*rowItem, true);
00993             (*rowPropertySet)["sorting"].clearModifiedFlag(); // this property should look "fresh"
00994             if (!rowItem->at(COLUMN_ID_VISIBLE).toBool()) //update
00995                 (*rowPropertySet)["visible"].setValue(QVariant(false,0), false/*rememberOldValue*/); 
00996         }
00997     }
00998 
00999     //5. Show fields for unused criterias (with "Visible" column set to false)
01000     KexiDB::BaseExpr *criteriaArgument; // <-- contains field or table.field
01001     for (QDictIterator<KexiDB::BaseExpr> it(criterias); (criteriaArgument = it.current()); ++it) {
01002         if (usedCriterias[it.current()])
01003             continue;
01004         //unused: append a new row
01005         KexiDB::BinaryExpr *criteriaExpr = criteriaArgument->parent()->toBinary();
01006         if (!criteriaExpr) {
01007             kexipluginswarn << "KexiQueryDesignerGuiEditor::showFieldsOrRelationsForQueryInternal(): "
01008                 "criteriaExpr is not a binary expr" << endl;
01009             continue;
01010         }
01011         KexiDB::VariableExpr *columnNameArgument = criteriaExpr->left()->toVariable(); //left or right
01012         if (!columnNameArgument) {
01013             columnNameArgument = criteriaExpr->right()->toVariable();
01014             if (!columnNameArgument) {
01015                 kexipluginswarn << "KexiQueryDesignerGuiEditor::showFieldsOrRelationsForQueryInternal(): "
01016                     "columnNameArgument is not a variable (table or table.field) expr" << endl;
01017                 continue;
01018             }
01019         }
01020         KexiDB::Field* field = 0;
01021         if (-1 == columnNameArgument->name.find('.') && query->tables()->count()==1) {
01022             //extreme case: only field name provided for one-table query:
01023             field = query->tables()->first()->field(columnNameArgument->name);
01024         }
01025         else {
01026             field = query->findTableField(columnNameArgument->name);
01027         }
01028 
01029         if (!field) {
01030             kexipluginswarn << "KexiQueryDesignerGuiEditor::showFieldsOrRelationsForQueryInternal(): "
01031                 "no columnInfo found in the query for name \"" << columnNameArgument->name << endl;
01032             continue;
01033         }
01034         QString tableName, fieldName, columnAlias, criteriaString;
01036         tableName = field->table()->name();
01037         fieldName = field->name();
01038         //create new row data
01039         KexiTableItem *newItem = createNewRow(tableName, fieldName, false /* !visible*/);
01040         if (criteriaExpr) {
01042             if (criteriaExpr->token()=='=')
01043                 criteriaString = criteriaArgument->toString();
01044             else
01045                 criteriaString = criteriaExpr->tokenToString() + " " + criteriaArgument->toString();
01046             (*newItem)[COLUMN_ID_CRITERIA] = criteriaString;
01047         }
01048         d->dataTable->dataAwareObject()->insertItem(newItem, row_num);
01049         //OK, row inserted: create a new set for it
01050         KoProperty::Set &set = *createPropertySet( row_num++, tableName, fieldName, true/*new one*/ );
01054         set["criteria"].setValue( criteriaString, false );
01055         set["visible"].setValue( QVariant(false,1), false );
01056     }
01057 
01058     //current property set has most probably changed
01059     propertySetSwitched();
01060 
01061     if (!was_dirty)
01062         setDirty(false);
01063     //move to 1st column, 1st row
01064     d->dataTable->dataAwareObject()->ensureCellVisible(0,0);
01065 //  tempData()->registerTableSchemaChanges(query);
01066 }
01067 
01068 bool KexiQueryDesignerGuiEditor::loadLayout()
01069 {
01070     QString xml;
01071 //  if (!loadDataBlock( xml, "query_layout" )) {
01072     loadDataBlock( xml, "query_layout" );
01073         //TODO errmsg
01074 //      return false;
01075 //  }
01076     if (xml.isEmpty()) {
01077         //in a case when query layout was not saved, build layout by hand
01078         // -- dynamic cast because of a need for handling invalid queries
01079         //    (as in KexiQueryDesignerGuiEditor::afterSwitchFrom()):
01080         KexiDB::QuerySchema * q =   dynamic_cast<KexiDB::QuerySchema *>(parentDialog()->schemaData());
01081         if (q) {
01082             showTablesForQuery( q );
01083             KexiDB::ResultInfo result;
01084             showRelationsForQuery( q, result );
01085             if (!result.success) {
01086                 parentDialog()->setStatus(&result, i18n("Query definition loading failed."));
01087                 return false;
01088             }
01089         }
01090         return true;
01091     }
01092 
01093     QDomDocument doc;
01094     doc.setContent(xml);
01095     QDomElement doc_el = doc.documentElement(), el;
01096     if (doc_el.tagName()!="query_layout") {
01097         //TODO errmsg
01098         return false;
01099     }
01100 
01101     const bool was_dirty = dirty();
01102 
01103     //add tables and relations to the relation view
01104     for (el = doc_el.firstChild().toElement(); !el.isNull(); el=el.nextSibling().toElement()) {
01105         if (el.tagName()=="table") {
01106             KexiDB::TableSchema *t = d->conn->tableSchema(el.attribute("name"));
01107             int x = el.attribute("x","-1").toInt();
01108             int y = el.attribute("y","-1").toInt();
01109             int width = el.attribute("width","-1").toInt();
01110             int height = el.attribute("height","-1").toInt();
01111             QRect rect;
01112             if (x!=-1 || y!=-1 || width!=-1 || height!=-1)
01113                 rect = QRect(x,y,width,height);
01114             d->relations->addTable( t, rect );
01115         }
01116         else if (el.tagName()=="conn") {
01117             SourceConnection src_conn;
01118             src_conn.masterTable = el.attribute("mtable");
01119             src_conn.masterField = el.attribute("mfield");
01120             src_conn.detailsTable = el.attribute("dtable");
01121             src_conn.detailsField = el.attribute("dfield");
01122             d->relations->addConnection(src_conn);
01123         }
01124     }
01125 
01126     if (!was_dirty)
01127         setDirty(false);
01128     return true;
01129 }
01130 
01131 bool KexiQueryDesignerGuiEditor::storeLayout()
01132 {
01133     KexiQueryPart::TempData * temp = tempData();
01134 
01135     // Save SQL without driver-escaped keywords.
01136     KexiDB::Connection* dbConn = mainWin()->project()->dbConnection();
01137     if (m_dialog->schemaData()) //set this instance as obsolete (only if it's stored)
01138         dbConn->setQuerySchemaObsolete( m_dialog->schemaData()->name() );
01139 
01140     KexiDB::Connection::SelectStatementOptions options;
01141     options.identifierEscaping = KexiDB::Driver::EscapeKexi|KexiDB::Driver::EscapeAsNecessary;
01142     QString sqlText = dbConn->selectStatement( *temp->query(), options );
01143     if (!storeDataBlock( sqlText, "sql" )) {
01144         return false;
01145     }
01146 
01147     //serialize detailed XML query definition
01148     QString xml = "<query_layout>", tmp;
01149     for (TablesDictIterator it(*d->relations->tables()); it.current(); ++it) {
01150         KexiRelationViewTableContainer *table_cont = it.current();
01152         tmp = QString("<table name=\"")+QString(table_cont->schema()->name())+"\" x=\""
01153          +QString::number(table_cont->x())
01154          +"\" y=\""+QString::number(table_cont->y())
01155          +"\" width=\""+QString::number(table_cont->width())
01156          +"\" height=\""+QString::number(table_cont->height())
01157          +"\"/>";
01158         xml += tmp;
01159     }
01160 
01161     KexiRelationViewConnection *con;
01162     for (ConnectionListIterator it(*d->relations->connections()); (con = it.current()); ++it) {
01163         tmp = QString("<conn mtable=\"") + QString(con->masterTable()->schema()->name())
01164             + "\" mfield=\"" + con->masterField() + "\" dtable=\""
01165             + QString(con->detailsTable()->schema()->name())
01166             + "\" dfield=\"" + con->detailsField() + "\"/>";
01167         xml += tmp;
01168     }
01169     xml += "</query_layout>";
01170     if (!storeDataBlock( xml, "query_layout" )) {
01171         return false;
01172     }
01173 
01174 //  mainWin()->project()->reloadPartItem( m_dialog );
01175 
01176     return true;
01177 }
01178 
01179 QSize KexiQueryDesignerGuiEditor::sizeHint() const
01180 {
01181     QSize s1 = d->relations->sizeHint();
01182     QSize s2 = d->head->sizeHint();
01183     return QSize(QMAX(s1.width(),s2.width()), s1.height()+s2.height());
01184 }
01185 
01186 KexiTableItem*
01187 KexiQueryDesignerGuiEditor::createNewRow(const QString& tableName, const QString& fieldName,
01188     bool visible) const
01189 {
01190     KexiTableItem *newItem = d->data->createItem();
01191     QString key;
01192     if (tableName=="*")
01193         key="*";
01194     else {
01195         if (!tableName.isEmpty())
01196             key = (tableName+".");
01197         key += fieldName;
01198     }
01199     (*newItem)[COLUMN_ID_COLUMN]=key;
01200     (*newItem)[COLUMN_ID_TABLE]=tableName;
01201     (*newItem)[COLUMN_ID_VISIBLE]=QVariant(visible, 1);
01202 #ifndef KEXI_NO_QUERY_TOTALS
01203     (*newItem)[COLUMN_ID_TOTALS]=QVariant(0);
01204 #endif
01205     return newItem;
01206 }
01207 
01208 void KexiQueryDesignerGuiEditor::slotDragOverTableRow(
01209     KexiTableItem * /*item*/, int /*row*/, QDragMoveEvent* e)
01210 {
01211     if (e->provides("kexi/field")) {
01212         e->acceptAction(true);
01213     }
01214 }
01215 
01216 void
01217 KexiQueryDesignerGuiEditor::slotDroppedAtRow(KexiTableItem * /*item*/, int /*row*/,
01218     QDropEvent *ev, KexiTableItem*& newItem)
01219 {
01220     QString sourceMimeType;
01221     QString srcTable;
01222     QString srcField;
01223 
01224     if (!KexiFieldDrag::decodeSingle(ev,sourceMimeType,srcTable,srcField))
01225         return;
01226     //insert new row at specific place
01227     newItem = createNewRow(srcTable, srcField, true /* visible*/);
01228     d->droppedNewItem = newItem;
01229     d->droppedNewTable = srcTable;
01230     d->droppedNewField = srcField;
01231     //TODO
01232 }
01233 
01234 void KexiQueryDesignerGuiEditor::slotNewItemAppendedForAfterDeletingInSpreadSheetMode()
01235 {
01236     KexiTableItem *item = d->data->last();
01237     if (item)
01238         item->at(COLUMN_ID_VISIBLE) = QVariant(false, 0); //the same init as in initTableRows()
01239 }
01240 
01241 void KexiQueryDesignerGuiEditor::slotRowInserted(KexiTableItem* item, uint row, bool /*repaint*/)
01242 {
01243     if (d->droppedNewItem && d->droppedNewItem==item) {
01244         createPropertySet( row, d->droppedNewTable, d->droppedNewField, true );
01245         propertySetSwitched();
01246         d->droppedNewItem=0;
01247     }
01248 }
01249 
01250 void KexiQueryDesignerGuiEditor::slotTableAdded(KexiDB::TableSchema & /*t*/)
01251 {
01252     if (!d->slotTableAdded_enabled)
01253         return;
01254     updateColumnsData();
01255     setDirty();
01256     d->dataTable->setFocus();
01257 }
01258 
01259 void KexiQueryDesignerGuiEditor::slotTableHidden(KexiDB::TableSchema & /*t*/)
01260 {
01261     updateColumnsData();
01262     setDirty();
01263 }
01264 
01266 QCString KexiQueryDesignerGuiEditor::generateUniqueAlias() const
01267 {
01268 //TODO: add option for using non-i18n'd "expr" prefix?
01269     const QCString expStr
01270         = i18n("short for 'expression' word (only latin letters, please)", "expr").latin1();
01271 //TODO: optimization: cache it?
01272     QAsciiDict<char> aliases(101);
01273     for (int r = 0; r<(int)d->sets->size(); r++) {
01274         KoProperty::Set *set = d->sets->at(r);
01275         if (set) {
01276             const QCString a = (*set)["alias"].value().toCString().lower();
01277             if (!a.isEmpty())
01278                 aliases.insert(a,(char*)1);
01279         }
01280     }
01281     int aliasNr=1;
01282     for (;;aliasNr++) {
01283         if (!aliases[expStr+QString::number(aliasNr).latin1()])
01284             break;
01285     }
01286     return expStr+QString::number(aliasNr).latin1();
01287 }
01288 
01290 KexiDB::BaseExpr*
01291 KexiQueryDesignerGuiEditor::parseExpressionString(const QString& fullString, int& token,
01292  bool allowRelationalOperator)
01293 {
01294     QString str = fullString.stripWhiteSpace();
01295     int len = 0;
01296     //KexiDB::BaseExpr *expr = 0;
01297     //1. get token
01298     token = 0;
01299     //2-char-long tokens
01300     if (str.startsWith(">="))
01301         token = GREATER_OR_EQUAL;
01302     else if (str.startsWith("<="))
01303         token = LESS_OR_EQUAL;
01304     else if (str.startsWith("<>"))
01305         token = NOT_EQUAL;
01306     else if (str.startsWith("!="))
01307         token = NOT_EQUAL2;
01308     else if (str.startsWith("=="))
01309         token = '=';
01310 
01311     if (token!=0)
01312         len = 2;
01313     else if (str.startsWith("=") //1-char-long tokens
01314         || str.startsWith("<")
01315         || str.startsWith(">"))
01316     {
01317         token = str[0].latin1();
01318         len = 1;
01319     }
01320     else {
01321         if (allowRelationalOperator)
01322             token = '=';
01323     }
01324 
01325     if (!allowRelationalOperator && token!=0)
01326         return 0;
01327 
01328     //1. get expression after token
01329     if (len>0)
01330         str = str.mid(len).stripWhiteSpace();
01331     if (str.isEmpty())
01332         return 0;
01333 
01334     KexiDB::BaseExpr *valueExpr = 0;
01335     QRegExp re;
01336     if (str.length()>=2 &&
01337         (
01338            (str.startsWith("\"") && str.endsWith("\""))
01339         || (str.startsWith("'") && str.endsWith("'")))
01340         )
01341     {
01342         valueExpr = new KexiDB::ConstExpr(CHARACTER_STRING_LITERAL, str.mid(1,str.length()-2));
01343     }
01344     else if (str.startsWith("[") && str.endsWith("]")) {
01345         valueExpr = new KexiDB::QueryParameterExpr(str.mid(1,str.length()-2));
01346     }
01347     else if ((re = QRegExp("(\\d{1,4})-(\\d{1,2})-(\\d{1,2})")).exactMatch( str ))
01348     {
01349             valueExpr = new KexiDB::ConstExpr(DATE_CONST, QDate::fromString(
01350                 re.cap(1).rightJustify(4, '0')+"-"+re.cap(2).rightJustify(2, '0')
01351                 +"-"+re.cap(3).rightJustify(2, '0'), Qt::ISODate));
01352     }
01353     else if ((re = QRegExp("(\\d{1,2}):(\\d{1,2})")).exactMatch( str )
01354           || (re = QRegExp("(\\d{1,2}):(\\d{1,2}):(\\d{1,2})")).exactMatch( str ))
01355     {
01356         QString res = re.cap(1).rightJustify(2, '0')+":"+re.cap(2).rightJustify(2, '0')
01357             +":"+re.cap(3).rightJustify(2, '0');
01358 //      kexipluginsdbg << res << endl;
01359         valueExpr = new KexiDB::ConstExpr(TIME_CONST, QTime::fromString(res, Qt::ISODate));
01360     }
01361     else if ((re = QRegExp("(\\d{1,4})-(\\d{1,2})-(\\d{1,2})\\s+(\\d{1,2}):(\\d{1,2})")).exactMatch( str )
01362           || (re = QRegExp("(\\d{1,4})-(\\d{1,2})-(\\d{1,2})\\s+(\\d{1,2}):(\\d{1,2}):(\\d{1,2})")).exactMatch( str ))
01363     {
01364         QString res = re.cap(1).rightJustify(4, '0')+"-"+re.cap(2).rightJustify(2, '0')
01365             +"-"+re.cap(3).rightJustify(2, '0')
01366             +"T"+re.cap(4).rightJustify(2, '0')+":"+re.cap(5).rightJustify(2, '0')
01367             +":"+re.cap(6).rightJustify(2, '0');
01368 //      kexipluginsdbg << res << endl;
01369         valueExpr = new KexiDB::ConstExpr(DATETIME_CONST,
01370             QDateTime::fromString(res, Qt::ISODate));
01371     }
01372     else if (str[0]>='0' && str[0]<='9' || str[0]=='-' || str[0]=='+') {
01373         //number
01374         QString decimalSym = KGlobal::locale()->decimalSymbol();
01375         bool ok;
01376         int pos = str.find('.');
01377         if (pos==-1) {//second chance: local decimal symbol
01378             pos = str.find(decimalSym);
01379         }
01380         if (pos>=0) {//real const number
01381             const int left = str.left(pos).toInt(&ok);
01382             if (!ok)
01383                 return 0;
01384             const int right = str.mid(pos+1).toInt(&ok);
01385             if (!ok)
01386                 return 0;
01387             valueExpr = new KexiDB::ConstExpr(REAL_CONST, QPoint(left,right)); //decoded to QPoint
01388         }
01389         else {
01390             //integer const
01391             const Q_LLONG val = str.toLongLong(&ok);
01392             if (!ok)
01393                 return 0;
01394             valueExpr = new KexiDB::ConstExpr(INTEGER_CONST, val);
01395         }
01396     }
01397     else if (str.lower()=="null") {
01398         valueExpr = new KexiDB::ConstExpr(SQL_NULL, QVariant());
01399     }
01400     else {//identfier
01401         if (!KexiUtils::isIdentifier(str))
01402             return 0;
01403         valueExpr = new KexiDB::VariableExpr(str);
01404         //find first matching field for name 'str':
01405         for (TablesDictIterator it(*d->relations->tables()); it.current(); ++it) {
01407             if (it.current()->schema()->table() && it.current()->schema()->table()->field(str)) {
01408                 valueExpr->toVariable()->field = it.current()->schema()->table()->field(str);
01409                 break;
01410             }
01411         }
01412     }
01413     return valueExpr;
01414 }
01415 
01416 void KexiQueryDesignerGuiEditor::slotBeforeCellChanged(KexiTableItem *item, int colnum,
01417     QVariant& newValue, KexiDB::ResultInfo* result)
01418 {
01419     if (colnum == COLUMN_ID_COLUMN) {
01420         if (newValue.isNull()) {
01421             d->data->updateRowEditBuffer(item, COLUMN_ID_TABLE, QVariant(), false);
01422             d->data->updateRowEditBuffer(item, COLUMN_ID_VISIBLE, QVariant(false,1));//invisible
01423             d->data->updateRowEditBuffer(item, COLUMN_ID_SORTING, QVariant());
01424 #ifndef KEXI_NO_QUERY_TOTALS
01425             d->data->updateRowEditBuffer(item, COLUMN_ID_TOTALS, QVariant());//remove totals
01426 #endif
01427             d->data->updateRowEditBuffer(item, COLUMN_ID_CRITERIA, QVariant());//remove crit.
01428             d->sets->removeCurrentPropertySet();
01429         }
01430         else {
01431             //auto fill 'table' column
01432             QString fieldId( newValue.toString().stripWhiteSpace() ); //tmp, can look like "table.field"
01433             QString fieldName; //"field" part of "table.field" or expression string
01434             QString tableName; //empty for expressions
01435             QCString alias;
01436             QString columnValueForExpr; //for setting pretty printed "alias: expr" in 1st column
01437             const bool isExpression = !d->fieldColumnIdentifiers[fieldId];
01438             if (isExpression) {
01439                 //this value is entered by hand and doesn't match
01440                 //any value in the combo box -- we're assuming this is an expression
01441                 //-table remains null
01442                 //-find "alias" in something like "alias : expr"
01443                 const int id = fieldId.find(':');
01444                 if (id>0) {
01445                     alias = fieldId.left(id).stripWhiteSpace().latin1();
01446                     if (!KexiUtils::isIdentifier(alias)) {
01447                         result->success = false;
01448                         result->allowToDiscardChanges = true;
01449                         result->column = colnum;
01450                         result->msg = i18n("Entered column alias \"%1\" is not a valid identifier.")
01451                             .arg(alias);
01452                         result->desc = i18n("Identifiers should start with a letter or '_' character");
01453                         return;
01454                     }
01455                 }
01456                 fieldName = fieldId.mid(id+1).stripWhiteSpace();
01457                 //check expr.
01458                 KexiDB::BaseExpr *e;
01459                 int dummyToken;
01460                 if ((e = parseExpressionString(fieldName, dummyToken, false/*allowRelationalOperator*/)))
01461                 {
01462                     fieldName = e->toString(); //print it prettier
01463                     //this is just checking: destroy expr. object
01464                     delete e;
01465                 }
01466                 else {
01467                     result->success = false;
01468                     result->allowToDiscardChanges = true;
01469                     result->column = colnum;
01470                     result->msg = i18n("Invalid expression \"%1\"").arg(fieldName);
01471                     return;
01472                 }
01473             }
01474             else {//not expr.
01475                 //this value is properly selected from combo box list
01476                 if (fieldId=="*") {
01477                     tableName = "*";
01478                 }
01479                 else {
01480                     if (!KexiDB::splitToTableAndFieldParts(
01481                         fieldId, tableName, fieldName, KexiDB::SetFieldNameIfNoTableName))
01482                     {
01483                         kexipluginswarn << "KexiQueryDesignerGuiEditor::slotBeforeCellChanged(): no 'field' or 'table.field'" << endl;
01484                         return;
01485                     }
01486                 }
01487             }
01488             bool saveOldValue = true;
01489             KoProperty::Set *set = d->sets->findPropertySetForItem(*item); //*propertyBuffer();
01490             if (!set) {
01491                 saveOldValue = false; // no old val.
01492                 const int row = d->data->findRef(item);
01493                 if (row<0) {
01494                     result->success = false;
01495                     return;
01496                 }
01497                 set = createPropertySet( row, tableName, fieldName, true );
01498                 propertySetSwitched();
01499             }
01500             d->data->updateRowEditBuffer(item, COLUMN_ID_TABLE, QVariant(tableName), false);
01501             d->data->updateRowEditBuffer(item, COLUMN_ID_VISIBLE, QVariant(true,1));
01502 #ifndef KEXI_NO_QUERY_TOTALS
01503             d->data->updateRowEditBuffer(item, COLUMN_ID_TOTALS, QVariant(0));
01504 #endif
01505             if (!sortingAllowed(fieldName, tableName)) {
01506                 // sorting is not available for "*" or "table.*" rows
01508                 d->data->updateRowEditBuffer(item, COLUMN_ID_SORTING, QVariant());
01509             }
01510             //update properties
01511             (*set)["field"].setValue(fieldName, saveOldValue);
01512             if (isExpression) {
01513                 //-no alias but it's needed:
01514                 if (alias.isEmpty()) //-try oto get old alias
01515                     alias = (*set)["alias"].value().toCString();
01516                 if (alias.isEmpty()) //-generate smallest unique alias
01517                     alias = generateUniqueAlias();
01518             }
01519             (*set)["isExpression"].setValue(QVariant(isExpression,1), saveOldValue);
01520             if (!alias.isEmpty()) {
01521                 (*set)["alias"].setValue(alias, saveOldValue);
01522                 //pretty printed "alias: expr"
01523                 newValue = QString(alias) + ": " + fieldName;
01524             }
01525             (*set)["caption"].setValue(QString::null, saveOldValue);
01526             (*set)["table"].setValue(tableName, saveOldValue);
01527             updatePropertiesVisibility(*set);
01528         }
01529     }
01530     else if (colnum==COLUMN_ID_TABLE) {
01531         if (newValue.isNull()) {
01532             if (!item->at(COLUMN_ID_COLUMN).toString().isEmpty())
01533                 d->data->updateRowEditBuffer(item, COLUMN_ID_COLUMN, QVariant(), false);
01534             d->data->updateRowEditBuffer(item, COLUMN_ID_VISIBLE, QVariant(false,1));//invisible
01535 #ifndef KEXI_NO_QUERY_TOTALS
01536             d->data->updateRowEditBuffer(item, COLUMN_ID_TOTALS, QVariant());//remove totals
01537 #endif
01538             d->data->updateRowEditBuffer(item, COLUMN_ID_CRITERIA, QVariant());//remove crit.
01539             d->sets->removeCurrentPropertySet();
01540         }
01541         //update property
01542         KoProperty::Set *set = d->sets->findPropertySetForItem(*item);
01543         if (set) {
01544             if ((*set)["isExpression"].value().toBool()==false) {
01545                 (*set)["table"] = newValue;
01546                 (*set)["caption"] = QString::null;
01547             }
01548             else {
01549                 //do not set table for expr. columns
01550                 newValue = QVariant();
01551             }
01552 //          KoProperty::Set &set = *propertyBuffer();
01553             updatePropertiesVisibility(*set);
01554         }
01555     }
01556     else if (colnum==COLUMN_ID_VISIBLE) {
01557         bool saveOldValue = true;
01558         if (!propertySet()) {
01559             saveOldValue = false;
01560             createPropertySet( d->dataTable->dataAwareObject()->currentRow(),
01561                 item->at(COLUMN_ID_TABLE).toString(), item->at(COLUMN_ID_COLUMN).toString(), true );
01562 #ifndef KEXI_NO_QUERY_TOTALS
01563             d->data->updateRowEditBuffer(item, COLUMN_ID_TOTALS, QVariant(0));//totals
01564 #endif
01565             propertySetSwitched();
01566         }
01567         KoProperty::Set &set = *propertySet();
01568         set["visible"].setValue(newValue, saveOldValue);
01569     }
01570 #ifndef KEXI_NO_QUERY_TOTALS
01571     else if (colnum==COLUMN_ID_TOTALS) {
01572         //TODO:
01573         //unused yet
01574         setDirty(true);
01575     }
01576 #endif
01577     else if (colnum==COLUMN_ID_SORTING) {
01578         KoProperty::Set *set = d->sets->findPropertySetForItem(*item);
01579         QString table( set->property("table").value().toString() );
01580         QString field( set->property("field").value().toString() );
01581         if (newValue.toInt()==0 || sortingAllowed(field, table)) {
01582             KoProperty::Property &property = set->property("sorting");
01583             QString key( property.listData()->keysAsStringList()[ newValue.toInt() ] );
01584             kexipluginsdbg << "new key=" << key << endl;
01585             property.setValue(key, true);
01586         }
01587         else { //show msg: sorting is not available
01588             result->success = false;
01589             result->allowToDiscardChanges = true;
01590             result->column = colnum;
01591             result->msg = i18n("Could not set sorting for multiple columns (%1)")
01592                 .arg(table=="*" ? table : (table+".*"));
01593         }
01594     }
01595     else if (colnum==COLUMN_ID_CRITERIA) {
01597         QString operatorStr, argStr;
01598         KexiDB::BaseExpr* e = 0;
01599         const QString str = newValue.toString().stripWhiteSpace();
01600         int token;
01601         QString field, table;
01602         KoProperty::Set *set = d->sets->findPropertySetForItem(*item);
01603         if (set) {
01604             field = (*set)["field"].value().toString();
01605             table = (*set)["table"].value().toString();
01606         }
01607         if (!str.isEmpty() && (!set || table=="*" || field.find("*")!=-1)) {
01608             //asterisk found! criteria not allowed
01609             result->success = false;
01610             result->allowToDiscardChanges = true;
01611             result->column = colnum;
01612             if (propertySet())
01613                 result->msg = i18n("Could not set criteria for \"%1\"")
01614                     .arg(table=="*" ? table : field);
01615             else
01616                 result->msg = i18n("Could not set criteria for empty row");
01617             //moved to result->allowToDiscardChanges handler //d->dataTable->dataAwareObject()->cancelEditor(); //prevents further editing of this cell
01618         }
01619         else if (str.isEmpty() || (e = parseExpressionString(str, token, true/*allowRelationalOperator*/)))
01620         {
01621             if (e) {
01622                 QString tokenStr;
01623                 if (token!='=') {
01624                     KexiDB::BinaryExpr be(KexiDBExpr_Relational, 0, token, 0);
01625                     tokenStr = be.tokenToString() + " ";
01626                 }
01627                 (*set)["criteria"] = tokenStr + e->toString(); //print it prettier
01628                 //this is just checking: destroy expr. object
01629                 delete e;
01630             }
01631             else if (str.isEmpty()) {
01632                 (*set)["criteria"] = QVariant(); //clear it
01633             }
01634             setDirty(true);
01635         }
01636         else {
01637             result->success = false;
01638             result->allowToDiscardChanges = true;
01639             result->column = colnum;
01640             result->msg = i18n("Invalid criteria \"%1\"").arg(newValue.toString());
01641         }
01642     }
01643 }
01644 
01645 void KexiQueryDesignerGuiEditor::slotTablePositionChanged(KexiRelationViewTableContainer*)
01646 {
01647     setDirty(true);
01648 }
01649 
01650 void KexiQueryDesignerGuiEditor::slotAboutConnectionRemove(KexiRelationViewConnection*)
01651 {
01652     setDirty(true);
01653 }
01654 
01655 void KexiQueryDesignerGuiEditor::slotTableFieldDoubleClicked(
01656     KexiDB::TableSchema* table, const QString& fieldName )
01657 {
01658     if (!table || (!table->field(fieldName) && fieldName!="*"))
01659         return;
01660     int row_num;
01661     //find last filled row in the GUI table
01662     for (row_num=d->sets->size()-1; row_num>=0 && !d->sets->at(row_num); row_num--)
01663         ;
01664     row_num++; //after
01665     //add row
01666     KexiTableItem *newItem = createNewRow(table->name(), fieldName, true /* visible*/);
01667     d->dataTable->dataAwareObject()->insertItem(newItem, row_num);
01668     d->dataTable->dataAwareObject()->setCursorPosition(row_num, 0);
01669     //create buffer
01670     createPropertySet( row_num, table->name(), fieldName, true/*new one*/ );
01671     propertySetSwitched();
01672     d->dataTable->setFocus();
01673 }
01674 
01675 KoProperty::Set *KexiQueryDesignerGuiEditor::propertySet()
01676 {
01677     return d->sets->currentPropertySet();
01678 }
01679 
01680 void KexiQueryDesignerGuiEditor::updatePropertiesVisibility(KoProperty::Set& set)
01681 {
01682     const bool asterisk = isAsterisk(
01683         set["table"].value().toString(), set["field"].value().toString()
01684     );
01685 #ifndef KEXI_NO_UNFINISHED
01686     set["caption"].setVisible( !asterisk );
01687 #endif
01688     set["alias"].setVisible( !asterisk );
01689 /*always invisible #ifndef KEXI_NO_UNFINISHED
01690     set["sorting"].setVisible( !asterisk );
01691 #endif*/
01692     propertySetReloaded(true);
01693 }
01694 
01695 KoProperty::Set*
01696 KexiQueryDesignerGuiEditor::createPropertySet( int row,
01697     const QString& tableName, const QString& fieldName, bool newOne )
01698 {
01699     //const bool asterisk = isAsterisk(tableName, fieldName);
01700     QString typeName = "KexiQueryDesignerGuiEditor::Column";
01701     KoProperty::Set *set = new KoProperty::Set(d->sets, typeName);
01702     KoProperty::Property *prop;
01703 
01704     //meta-info for property editor
01705     set->addProperty(prop = new KoProperty::Property("this:classString", i18n("Query column")) );
01706     prop->setVisible(false);
01708 //  prop->setVisible(false);
01709 
01710     set->addProperty(prop = new KoProperty::Property("table", QVariant(tableName)) );
01711     prop->setVisible(false);//always hidden
01712 
01713     set->addProperty(prop = new KoProperty::Property("field", QVariant(fieldName)) );
01714     prop->setVisible(false);//always hidden
01715 
01716     set->addProperty(prop = new KoProperty::Property("caption", QVariant(QString::null), i18n("Caption") ) );
01717 #ifdef KEXI_NO_UNFINISHED
01718         prop->setVisible(false);
01719 #endif
01720 
01721     set->addProperty(prop = new KoProperty::Property("alias", QVariant(QString::null), i18n("Alias")) );
01722 
01723     set->addProperty(prop = new KoProperty::Property("visible", QVariant(true, 4)) );
01724     prop->setVisible(false);
01725 
01726 /*TODO:
01727     set->addProperty(prop = new KexiProperty("totals", QVariant(QString::null)) );
01728     prop->setVisible(false);*/
01729 
01730     //sorting
01731     QStringList slist, nlist;
01732     slist << "nosorting" << "ascending" << "descending";
01733     nlist << i18n("None") << i18n("Ascending") << i18n("Descending");
01734     set->addProperty(prop = new KoProperty::Property("sorting",
01735         slist, nlist, *slist.at(0), i18n("Sorting")));
01736     prop->setVisible(false);
01737 
01738     set->addProperty(prop = new KoProperty::Property("criteria", QVariant(QString::null)) );
01739     prop->setVisible(false);
01740 
01741     set->addProperty(prop = new KoProperty::Property("isExpression", QVariant(false, 1)) );
01742     prop->setVisible(false);
01743 
01744     connect(set, SIGNAL(propertyChanged(KoProperty::Set&, KoProperty::Property&)),
01745         this, SLOT(slotPropertyChanged(KoProperty::Set&, KoProperty::Property&)));
01746 
01747     d->sets->insert(row, set, newOne);
01748 
01749     updatePropertiesVisibility(*set);
01750     return set;
01751 }
01752 
01753 void KexiQueryDesignerGuiEditor::setFocus()
01754 {
01755     d->dataTable->setFocus();
01756 }
01757 
01758 void KexiQueryDesignerGuiEditor::slotPropertyChanged(KoProperty::Set& set, KoProperty::Property& property)
01759 {
01760     const QCString& pname = property.name();
01761 /*
01762  * TODO (js) use KexiProperty::setValidator(QString) when implemented as described in TODO #60
01763  */
01764     if (pname=="alias" || pname=="name") {
01765         const QVariant& v = property.value();
01766         if (!v.toString().stripWhiteSpace().isEmpty() && !KexiUtils::isIdentifier( v.toString() )) {
01767             KMessageBox::sorry(this,
01768                 KexiUtils::identifierExpectedMessage(property.caption(), v.toString()));
01769             property.resetValue();
01770         }
01771         if (pname=="alias") {
01772             if (set["isExpression"].value().toBool()==true) {
01773                 //update value in column #1
01774                 d->dataTable->dataAwareObject()->acceptEditor();
01775 //              d->dataTable->dataAwareObject()->setCursorPosition(d->dataTable->dataAwareObject()->currentRow(),0);
01776                 //d->dataTable->dataAwareObject()->startEditCurrentCell();
01777                 d->data->updateRowEditBuffer(d->dataTable->dataAwareObject()->selectedItem(),
01778                     0, QVariant(set["alias"].value().toString() + ": " + set["field"].value().toString()));
01779                 d->data->saveRowChanges(*d->dataTable->dataAwareObject()->selectedItem(), true);
01780 //              d->dataTable->dataAwareObject()->acceptRowEdit();
01781             }
01782         }
01783     }
01784 }
01785 
01786 void KexiQueryDesignerGuiEditor::slotNewItemStored(KexiPart::Item& item)
01787 {
01788     d->relations->objectCreated(item.mimeType(), item.name().latin1());
01789 }
01790 
01791 void KexiQueryDesignerGuiEditor::slotItemRemoved(const KexiPart::Item& item)
01792 {
01793     d->relations->objectDeleted(item.mimeType(), item.name().latin1());
01794 }
01795 
01796 void KexiQueryDesignerGuiEditor::slotItemRenamed(const KexiPart::Item& item, const QCString& oldName)
01797 {
01798     d->relations->objectRenamed(item.mimeType(), oldName, item.name().latin1());
01799 }
01800 
01801 #include "kexiquerydesignerguieditor.moc"
01802 
KDE Home | KDE Accessibility Home | Description of Access Keys