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