kexi

kexiquerydesignersql.cpp

00001 /* This file is part of the KDE project
00002    Copyright (C) 2003 Lucijan Busch <lucijan@kde.org>
00003    Copyright (C) 2004-2006 Jaroslaw Staniek <js@iidea.pl>
00004 
00005    This program 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 program 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 program; see the file COPYING.  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 <qsplitter.h>
00022 #include <qlayout.h>
00023 #include <qhbox.h>
00024 #include <qvbox.h>
00025 #include <qtimer.h>
00026 
00027 #include <kapplication.h>
00028 #include <kdebug.h>
00029 #include <kmessagebox.h>
00030 #include <kiconloader.h>
00031 
00032 #include <kexiutils/utils.h>
00033 #include <kexidb/driver.h>
00034 #include <kexidb/connection.h>
00035 #include <kexidb/parser/parser.h>
00036 
00037 #include <kexiproject.h>
00038 #include <keximainwindow.h>
00039 
00040 #include "kexiquerydesignersqleditor.h"
00041 #include "kexiquerydesignersqlhistory.h"
00042 #include "kexiquerydesignersql.h"
00043 #include "kexiquerypart.h"
00044 
00045 #include "kexisectionheader.h"
00046 
00047 
00048 static bool compareSQL(const QString& sql1, const QString& sql2)
00049 {
00050     //TODO: use reformatting functions here
00051     return sql1.stripWhiteSpace()==sql2.stripWhiteSpace();
00052 }
00053 
00054 //===================
00055 
00057 class KexiQueryDesignerSQLView::Private
00058 {
00059     public:
00060         Private() :
00061            history(0)
00062          , historyHead(0)
00063          , statusPixmapOk( DesktopIcon("button_ok") )
00064          , statusPixmapErr( DesktopIcon("button_cancel") )
00065          , statusPixmapInfo( DesktopIcon("messagebox_info") )
00066          , parsedQuery(0)
00067          , heightForStatusMode(-1)
00068          , heightForHistoryMode(-1)
00069          , eventFilterForSplitterEnabled(true)
00070          , justSwitchedFromNoViewMode(false)
00071          , slotTextChangedEnabled(true)
00072         {
00073         }
00074         KexiQueryDesignerSQLEditor *editor;
00075         KexiQueryDesignerSQLHistory *history;
00076         QLabel *pixmapStatus, *lblStatus;
00077         QHBox *status_hbox;
00078         QVBox *history_section;
00079         KexiSectionHeader *head, *historyHead;
00080         QPixmap statusPixmapOk, statusPixmapErr, statusPixmapInfo;
00081         QSplitter *splitter;
00082         KToggleAction *action_toggle_history;
00085         KexiDB::QuerySchema *parsedQuery;
00087         QString origStatement;
00089         int heightForStatusMode, heightForHistoryMode;
00091         bool action_toggle_history_was_checked : 1;
00093         bool eventFilterForSplitterEnabled : 1;
00095         bool justSwitchedFromNoViewMode : 1;
00097         bool slotTextChangedEnabled : 1;
00098 };
00099 
00100 //===================
00101 
00102 KexiQueryDesignerSQLView::KexiQueryDesignerSQLView(KexiMainWindow *mainWin, QWidget *parent, const char *name)
00103  : KexiViewBase(mainWin, parent, name)
00104  , d( new Private() )
00105 {
00106     d->splitter = new QSplitter(this);
00107     d->splitter->setOrientation(Vertical);
00108     d->head = new KexiSectionHeader(i18n("SQL Query Text"), Vertical, d->splitter);
00109     d->editor = new KexiQueryDesignerSQLEditor(mainWin, d->head, "sqle");
00110 //  d->editor->installEventFilter(this);//for keys
00111     connect(d->editor, SIGNAL(textChanged()), this, SLOT(slotTextChanged()));
00112     addChildView(d->editor);
00113     setViewWidget(d->editor);
00114     d->splitter->setFocusProxy(d->editor);
00115     setFocusProxy(d->editor);
00116 
00117     d->history_section = new QVBox(d->splitter);
00118 
00119     d->status_hbox = new QHBox(d->history_section);
00120     d->status_hbox->installEventFilter(this);
00121     d->splitter->setResizeMode(d->history_section, QSplitter::KeepSize);
00122     d->status_hbox->setSpacing(0);
00123     d->pixmapStatus = new QLabel(d->status_hbox);
00124     d->pixmapStatus->setFixedWidth(d->statusPixmapOk.width()*3/2);
00125     d->pixmapStatus->setAlignment(AlignHCenter | AlignTop);
00126     d->pixmapStatus->setMargin(d->statusPixmapOk.width()/4);
00127     d->pixmapStatus->setPaletteBackgroundColor( palette().active().color(QColorGroup::Base) );
00128 
00129     d->lblStatus = new QLabel(d->status_hbox);
00130     d->lblStatus->setAlignment(AlignLeft | AlignTop | WordBreak);
00131     d->lblStatus->setMargin(d->statusPixmapOk.width()/4);
00132     d->lblStatus->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Expanding );
00133     d->lblStatus->resize(d->lblStatus->width(),d->statusPixmapOk.width()*3);
00134     d->lblStatus->setPaletteBackgroundColor( palette().active().color(QColorGroup::Base) );
00135 
00136     QHBoxLayout *b = new QHBoxLayout(this);
00137     b->addWidget(d->splitter);
00138 
00139     plugSharedAction("querypart_check_query", this, SLOT(slotCheckQuery())); 
00140     plugSharedAction("querypart_view_toggle_history", this, SLOT(slotUpdateMode()));
00141     d->action_toggle_history = static_cast<KToggleAction*>( sharedAction( "querypart_view_toggle_history" ) );
00142 
00143     d->historyHead = new KexiSectionHeader(i18n("SQL Query History"), Vertical, d->history_section);
00144     d->historyHead->installEventFilter(this);
00145     d->history = new KexiQueryDesignerSQLHistory(d->historyHead, "sql_history");
00146 
00147     static const QString msg_back = i18n("Back to Selected Query");
00148     static const QString msg_clear = i18n("Clear History");
00149     d->historyHead->addButton("select_item", msg_back, this, SLOT(slotSelectQuery()));
00150     d->historyHead->addButton("editclear", msg_clear, d->history, SLOT(clear()));
00151     d->history->popupMenu()->insertItem(SmallIcon("select_item"), msg_back, this, SLOT(slotSelectQuery()));
00152     d->history->popupMenu()->insertItem(SmallIcon("editclear"), msg_clear, d->history, SLOT(clear()));
00153     connect(d->history, SIGNAL(currentItemDoubleClicked()), this, SLOT(slotSelectQuery()));
00154 
00155     d->heightForHistoryMode = -1; //height() / 2;
00156     //d->historyHead->hide();
00157     d->action_toggle_history_was_checked = !d->action_toggle_history->isChecked(); //to force update
00158     slotUpdateMode();
00159     slotCheckQuery();
00160 }
00161 
00162 KexiQueryDesignerSQLView::~KexiQueryDesignerSQLView()
00163 {
00164     delete d;
00165 }
00166 
00167 KexiQueryDesignerSQLEditor *KexiQueryDesignerSQLView::editor() const
00168 {
00169     return d->editor;
00170 }
00171 
00172 void KexiQueryDesignerSQLView::setStatusOk()
00173 {
00174     d->pixmapStatus->setPixmap(d->statusPixmapOk);
00175     setStatusText("<h2>"+i18n("The query is correct")+"</h2>");
00176     d->history->addEvent(d->editor->text().stripWhiteSpace(), true, QString::null);
00177 }
00178 
00179 void KexiQueryDesignerSQLView::setStatusError(const QString& msg)
00180 {
00181     d->pixmapStatus->setPixmap(d->statusPixmapErr);
00182     setStatusText("<h2>"+i18n("The query is incorrect")+"</h2><p>"+msg+"</p>");
00183     d->history->addEvent(d->editor->text().stripWhiteSpace(), false, msg);
00184 }
00185 
00186 void KexiQueryDesignerSQLView::setStatusEmpty()
00187 {
00188     d->pixmapStatus->setPixmap(d->statusPixmapInfo);
00189     setStatusText(i18n("Please enter your query and execute \"Check query\" function to verify it."));
00190 }
00191 
00192 void KexiQueryDesignerSQLView::setStatusText(const QString& text)
00193 {
00194     if (!d->action_toggle_history->isChecked()) {
00195         QSimpleRichText rt(text, d->lblStatus->font());
00196         rt.setWidth(d->lblStatus->width());
00197         QValueList<int> sz = d->splitter->sizes();
00198         const int newHeight = rt.height()+d->lblStatus->margin()*2;
00199         if (sz[1]<newHeight) {
00200             sz[1] = newHeight;
00201             d->splitter->setSizes(sz);
00202         }
00203         d->lblStatus->setText(text);
00204     }
00205 }
00206 
00207 tristate
00208 KexiQueryDesignerSQLView::beforeSwitchTo(int mode, bool &dontStore)
00209 {
00210 //TODO
00211     dontStore = true;
00212     if (mode==Kexi::DesignViewMode || mode==Kexi::DataViewMode) {
00213         QString sqlText = d->editor->text().stripWhiteSpace();
00214         KexiQueryPart::TempData * temp = tempData();
00215         if (sqlText.isEmpty()) {
00216             //special case: empty SQL text
00217             if (temp->query()) {
00218                 temp->queryChangedInPreviousView = true; //query changed
00219                 temp->setQuery(0);
00220 //              delete temp->query; //safe?
00221 //              temp->query = 0;
00222             }
00223         }
00224         else {
00225             const bool designViewWasVisible = parentDialog()->viewForMode(mode)!=0;
00226             //should we check SQL text?
00227             if (designViewWasVisible 
00228                 && !d->justSwitchedFromNoViewMode //unchanged, but we should check SQL text
00229                 && compareSQL(d->origStatement, d->editor->text())) {
00230                 //statement unchanged! - nothing to do
00231                 temp->queryChangedInPreviousView = false;
00232             }
00233             else {
00234                 //yes: parse SQL text
00235                 if (!slotCheckQuery()) {
00236                     if (KMessageBox::No==KMessageBox::warningYesNo(this, "<p>"+i18n("The query you entered is incorrect.")
00237                         +"</p><p>"+i18n("Do you want to cancel any changes made to this SQL text?")+"</p>"
00238                         +"</p><p>"+i18n("Answering \"No\" allows you to make corrections.")+"</p>"))
00239                     {
00240                         return cancelled;
00241                     }
00242                     //do not change original query - it's invalid
00243                     temp->queryChangedInPreviousView = false;
00244                     //this view is no longer _just_ switched from "NoViewMode"
00245                     d->justSwitchedFromNoViewMode = false;
00246                     return true;
00247                 }
00248                 //this view is no longer _just_ switched from "NoViewMode"
00249                 d->justSwitchedFromNoViewMode = false;
00250                 //replace old query schema with new one
00251                 temp->setQuery( d->parsedQuery ); //this will also delete temp->query()
00252 //              delete temp->query; //safe?
00253 //              temp->query = d->parsedQuery;
00254                 d->parsedQuery = 0;
00255                 temp->queryChangedInPreviousView = true;
00256             }
00257         }
00258     }
00259 
00260     //TODO
00261     /*
00262     if (d->doc) {
00263         KexiDB::Parser *parser = new KexiDB::Parser(mainWin()->project()->dbConnection());
00264         parser->parse(getQuery());
00265         d->doc->setSchema(parser->select());
00266 
00267         if(parser->operation() == KexiDB::Parser::OP_Error)
00268         {
00269             d->history->addEvent(getQuery(), false, parser->error().error());
00270             kdDebug() << "KexiQueryDesignerSQLView::beforeSwitchTo(): syntax error!" << endl;
00271             return false;
00272         }
00273         delete parser;
00274     }
00275 
00276     setDirty(true);*/
00277 //  if (parentDialog()->hasFocus())
00278     d->editor->setFocus();
00279     return true;
00280 }
00281 
00282 tristate
00283 KexiQueryDesignerSQLView::afterSwitchFrom(int mode)
00284 {
00285     kdDebug() << "KexiQueryDesignerSQLView::afterSwitchFrom()" << endl;
00286 //  if (mode==Kexi::DesignViewMode || mode==Kexi::DataViewMode) {
00287     if (mode==Kexi::NoViewMode) {
00288         //User opened text view _directly_. 
00289         //This flag is set to indicate for beforeSwitchTo() that even if text has not been changed,
00290         //SQL text should be invalidated.
00291         d->justSwitchedFromNoViewMode = true;
00292     }
00293     KexiQueryPart::TempData * temp = tempData();
00294     KexiDB::QuerySchema *query = temp->query();
00295     if (!query) {//try to just get saved schema, instead of temporary one
00296         query = dynamic_cast<KexiDB::QuerySchema *>(parentDialog()->schemaData());
00297     }
00298 
00299     if (mode!=0/*failure only if it is switching from prev. view*/ && !query) {
00300         //TODO msg
00301         return false;
00302     }
00303 
00304     if (!query) {
00305         //no valid query schema delivered: just load sql text, no matter if it's valid
00306         if (!loadDataBlock( d->origStatement, "sql", true /*canBeEmpty*/ ))
00307             return false;
00308     }
00309     else {
00310     // Use query with Kexi keywords (but not driver-specific keywords) escaped.
00311         temp->setQuery( query );
00312 //      temp->query = query;
00313         KexiDB::Connection* conn = mainWin()->project()->dbConnection();
00314         KexiDB::Connection::SelectStatementOptions options;
00315         options.identifierEscaping = KexiDB::Driver::EscapeKexi;
00316         options.addVisibleLookupColumns = false;
00317         d->origStatement = conn->selectStatement(*query, options).stripWhiteSpace();
00318     }
00319 
00320     d->slotTextChangedEnabled = false;
00321      d->editor->setText( d->origStatement );
00322     d->slotTextChangedEnabled = true;
00323     QTimer::singleShot(100, d->editor, SLOT(setFocus()));
00324     return true;
00325 }
00326 
00327 QString
00328 KexiQueryDesignerSQLView::sqlText() const
00329 {
00330     return d->editor->text();
00331 }
00332 
00333 bool KexiQueryDesignerSQLView::slotCheckQuery()
00334 {
00335     QString sqlText( d->editor->text().stripWhiteSpace() );
00336     if (sqlText.isEmpty()) {
00337         delete d->parsedQuery;
00338         d->parsedQuery = 0;
00339         setStatusEmpty();
00340         return true;
00341     }
00342 
00343     kdDebug() << "KexiQueryDesignerSQLView::slotCheckQuery()" << endl;
00344     //KexiQueryPart::TempData * temp = tempData();
00345     KexiDB::Parser *parser = mainWin()->project()->sqlParser();
00346     const bool ok = parser->parse( sqlText );
00347     delete d->parsedQuery;
00348     d->parsedQuery = parser->query();
00349     if (!d->parsedQuery || !ok || !parser->error().type().isEmpty()) {
00350         KexiDB::ParserError err = parser->error();
00351         setStatusError(err.error());
00352         d->editor->jump(err.at());
00353         delete d->parsedQuery;
00354         d->parsedQuery = 0;
00355         return false;
00356     }
00357 
00358     setStatusOk();
00359     return true;
00360 }
00361 
00362 void KexiQueryDesignerSQLView::slotUpdateMode()
00363 {
00364     if (d->action_toggle_history->isChecked() == d->action_toggle_history_was_checked)
00365         return;
00366 
00367     d->eventFilterForSplitterEnabled = false;
00368 
00369     QValueList<int> sz = d->splitter->sizes();
00370     d->action_toggle_history_was_checked = d->action_toggle_history->isChecked();
00371     int heightToSet = -1;
00372     if (d->action_toggle_history->isChecked()) {
00373         d->status_hbox->hide();
00374         d->historyHead->show();
00375         d->history->show();
00376         if (d->heightForHistoryMode==-1)
00377             d->heightForHistoryMode = m_dialog->height() / 2;
00378         heightToSet = d->heightForHistoryMode;
00379         d->heightForStatusMode = sz[1]; //remember
00380     }
00381     else {
00382         if (d->historyHead)
00383             d->historyHead->hide();
00384         d->status_hbox->show();
00385         if (d->heightForStatusMode>=0) {
00386             heightToSet = d->heightForStatusMode;
00387         } else {
00388             d->heightForStatusMode = d->status_hbox->height();
00389         }
00390         if (d->heightForHistoryMode>=0)
00391             d->heightForHistoryMode = sz[1];
00392     }
00393     
00394     if (heightToSet>=0) {
00395         sz[1] = heightToSet;
00396         d->splitter->setSizes(sz);
00397     }
00398     d->eventFilterForSplitterEnabled = true;
00399     slotCheckQuery();
00400 }
00401 
00402 void KexiQueryDesignerSQLView::slotTextChanged()
00403 {
00404     if (!d->slotTextChangedEnabled)
00405         return;
00406     setDirty(true);
00407     setStatusEmpty();
00408 }
00409 
00410 bool KexiQueryDesignerSQLView::eventFilter( QObject *o, QEvent *e )
00411 {
00412     if (d->eventFilterForSplitterEnabled) {
00413         if (e->type()==QEvent::Resize && o && o==d->historyHead && d->historyHead->isVisible()) {
00414             d->heightForHistoryMode = d->historyHead->height();
00415         }
00416         else if (e->type()==QEvent::Resize && o && o==d->status_hbox && d->status_hbox->isVisible()) {
00417             d->heightForStatusMode = d->status_hbox->height();
00418         }
00419     }
00420     return KexiViewBase::eventFilter(o, e);
00421 }
00422 
00423 void KexiQueryDesignerSQLView::updateActions(bool activated)
00424 {
00425     if (activated) {
00426         slotUpdateMode();
00427     }
00428     setAvailable("querypart_check_query", true);
00429     setAvailable("querypart_view_toggle_history", true);
00430     KexiViewBase::updateActions(activated);
00431 }
00432 
00433 void KexiQueryDesignerSQLView::slotSelectQuery()
00434 {
00435     QString sql = d->history->selectedStatement();
00436     if (!sql.isEmpty()) {
00437         d->editor->setText( sql );
00438     }
00439 }
00440 
00441 KexiQueryPart::TempData *
00442 KexiQueryDesignerSQLView::tempData() const
00443 {   
00444     return dynamic_cast<KexiQueryPart::TempData*>(parentDialog()->tempData());
00445 }
00446 
00447 KexiDB::SchemaData*
00448 KexiQueryDesignerSQLView::storeNewData(const KexiDB::SchemaData& sdata, bool &cancel)
00449 {
00450     Q_UNUSED( cancel );
00451 
00452     //here: we won't store query layout: it will be recreated 'by hand' in GUI Query Editor
00453     bool queryOK = slotCheckQuery();
00454     bool ok = true;
00455     KexiDB::SchemaData* query = 0;
00456     if (queryOK) {
00457         //query is ok
00458         if (d->parsedQuery) {
00459             query = d->parsedQuery; //will be returned, so: don't keep it
00460             d->parsedQuery = 0;
00461         }
00462         else {//empty query
00463             query = new KexiDB::SchemaData(); //just empty
00464         }
00465 
00466         (KexiDB::SchemaData&)*query = sdata; //copy main attributes
00467         ok = m_mainWin->project()->dbConnection()->storeObjectSchemaData( *query, true /*newObject*/ );
00468         if (ok) {
00469             m_dialog->setId( query->id() );
00470             ok = storeDataBlock( d->editor->text(), "sql" );
00471         }
00472     }
00473     else {
00474         //query is not ok
00475 //#if 0
00476     //TODO: allow saving invalid queries
00477     //TODO: just ask this question:
00478         query = new KexiDB::SchemaData(); //just empty
00479 
00480         ok = (KMessageBox::questionYesNo(this, i18n("Do you want to save invalid query?"),
00481             0, KStdGuiItem::yes(), KStdGuiItem::no(), "askBeforeSavingInvalidQueries"/*config entry*/)==KMessageBox::Yes);
00482         if (ok) {
00483             (KexiDB::SchemaData&)*query = sdata; //copy main attributes
00484             ok = m_mainWin->project()->dbConnection()->storeObjectSchemaData( *query, true /*newObject*/ );
00485         }
00486         if (ok) {
00487             m_dialog->setId( query->id() );
00488             ok = storeDataBlock( d->editor->text(), "sql" );
00489         }
00490 //#else
00491         //ok = false;
00492 //#endif
00493     }
00494     if (!ok) {
00495         delete query;
00496         query = 0;
00497     }
00498     return query;
00499 }
00500 
00501 tristate KexiQueryDesignerSQLView::storeData(bool dontAsk)
00502 {
00503     tristate res = KexiViewBase::storeData(dontAsk);
00504     if (~res)
00505         return res;
00506     if (res == true) {
00507         res = storeDataBlock( d->editor->text(), "sql" );
00508 #if 0
00509         bool queryOK = slotCheckQuery();
00510         if (queryOK) {
00511             res = storeDataBlock( d->editor->text(), "sql" );
00512         }
00513         else {
00514             //query is not ok
00515             //TODO: allow saving invalid queries
00516             //TODO: just ask this question:
00517             res = false;
00518         }
00519 #endif
00520     }
00521     if (res == true) {
00522         QString empty_xml;
00523         res = storeDataBlock( empty_xml, "query_layout" ); //clear
00524     }
00525     if (!res)
00526         setDirty(true);
00527     return res;
00528 }
00529 
00530 
00531 /*void KexiQueryDesignerSQLView::slotHistoryHeaderButtonClicked(const QString& buttonIdentifier)
00532 {
00533     if (buttonIdentifier=="select_query") {
00534         slotSelectQuery();
00535     }
00536     else if (buttonIdentifier=="clear_history") {
00537         d->history->clear();
00538     }
00539 }*/
00540 
00541 #include "kexiquerydesignersql.moc"
00542 
KDE Home | KDE Accessibility Home | Description of Access Keys