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 wil 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         d->origStatement = conn->selectStatement(*query, options).stripWhiteSpace();
00317     }
00318 
00319     d->slotTextChangedEnabled = false;
00320      d->editor->setText( d->origStatement );
00321     d->slotTextChangedEnabled = true;
00322     QTimer::singleShot(100, d->editor, SLOT(setFocus()));
00323     return true;
00324 }
00325 
00326 QString
00327 KexiQueryDesignerSQLView::sqlText() const
00328 {
00329     return d->editor->text();
00330 }
00331 
00332 bool KexiQueryDesignerSQLView::slotCheckQuery()
00333 {
00334     QString sqlText( d->editor->text().stripWhiteSpace() );
00335     if (sqlText.isEmpty()) {
00336         delete d->parsedQuery;
00337         d->parsedQuery = 0;
00338         setStatusEmpty();
00339         return true;
00340     }
00341 
00342     kdDebug() << "KexiQueryDesignerSQLView::slotCheckQuery()" << endl;
00343     //KexiQueryPart::TempData * temp = tempData();
00344     KexiDB::Parser *parser = mainWin()->project()->sqlParser();
00345     const bool ok = parser->parse( sqlText );
00346     delete d->parsedQuery;
00347     d->parsedQuery = parser->query();
00348     if (!d->parsedQuery || !ok || !parser->error().type().isEmpty()) {
00349         KexiDB::ParserError err = parser->error();
00350         setStatusError(err.error());
00351         d->editor->jump(err.at());
00352         delete d->parsedQuery;
00353         d->parsedQuery = 0;
00354         return false;
00355     }
00356 
00357     setStatusOk();
00358     return true;
00359 }
00360 
00361 void KexiQueryDesignerSQLView::slotUpdateMode()
00362 {
00363     if (d->action_toggle_history->isChecked() == d->action_toggle_history_was_checked)
00364         return;
00365 
00366     d->eventFilterForSplitterEnabled = false;
00367 
00368     QValueList<int> sz = d->splitter->sizes();
00369     d->action_toggle_history_was_checked = d->action_toggle_history->isChecked();
00370     int heightToSet = -1;
00371     if (d->action_toggle_history->isChecked()) {
00372         d->status_hbox->hide();
00373         d->historyHead->show();
00374         d->history->show();
00375         if (d->heightForHistoryMode==-1)
00376             d->heightForHistoryMode = m_dialog->height() / 2;
00377         heightToSet = d->heightForHistoryMode;
00378         d->heightForStatusMode = sz[1]; //remember
00379     }
00380     else {
00381         if (d->historyHead)
00382             d->historyHead->hide();
00383         d->status_hbox->show();
00384         if (d->heightForStatusMode>=0) {
00385             heightToSet = d->heightForStatusMode;
00386         } else {
00387             d->heightForStatusMode = d->status_hbox->height();
00388         }
00389         if (d->heightForHistoryMode>=0)
00390             d->heightForHistoryMode = sz[1];
00391     }
00392     
00393     if (heightToSet>=0) {
00394         sz[1] = heightToSet;
00395         d->splitter->setSizes(sz);
00396     }
00397     d->eventFilterForSplitterEnabled = true;
00398     slotCheckQuery();
00399 }
00400 
00401 void KexiQueryDesignerSQLView::slotTextChanged()
00402 {
00403     if (!d->slotTextChangedEnabled)
00404         return;
00405     setDirty(true);
00406     setStatusEmpty();
00407 }
00408 
00409 bool KexiQueryDesignerSQLView::eventFilter( QObject *o, QEvent *e )
00410 {
00411     if (d->eventFilterForSplitterEnabled) {
00412         if (e->type()==QEvent::Resize && o && o==d->historyHead && d->historyHead->isVisible()) {
00413             d->heightForHistoryMode = d->historyHead->height();
00414         }
00415         else if (e->type()==QEvent::Resize && o && o==d->status_hbox && d->status_hbox->isVisible()) {
00416             d->heightForStatusMode = d->status_hbox->height();
00417         }
00418     }
00419     return KexiViewBase::eventFilter(o, e);
00420 }
00421 
00422 void KexiQueryDesignerSQLView::updateActions(bool activated)
00423 {
00424     if (activated) {
00425         slotUpdateMode();
00426     }
00427     setAvailable("querypart_check_query", true);
00428     setAvailable("querypart_view_toggle_history", true);
00429     KexiViewBase::updateActions(activated);
00430 }
00431 
00432 void KexiQueryDesignerSQLView::slotSelectQuery()
00433 {
00434     QString sql = d->history->selectedStatement();
00435     if (!sql.isEmpty()) {
00436         d->editor->setText( sql );
00437     }
00438 }
00439 
00440 KexiQueryPart::TempData *
00441 KexiQueryDesignerSQLView::tempData() const
00442 {   
00443     return dynamic_cast<KexiQueryPart::TempData*>(parentDialog()->tempData());
00444 }
00445 
00446 KexiDB::SchemaData*
00447 KexiQueryDesignerSQLView::storeNewData(const KexiDB::SchemaData& sdata, bool &cancel)
00448 {
00449     Q_UNUSED( cancel );
00450 
00451     //here: we won't store query layout: it will be recreated 'by hand' in GUI Query Editor
00452     bool queryOK = slotCheckQuery();
00453     bool ok = true;
00454     KexiDB::SchemaData* query = 0;
00455     if (queryOK) {
00456         //query is ok
00457         if (d->parsedQuery) {
00458             query = d->parsedQuery; //will be returned, so: don't keep it
00459             d->parsedQuery = 0;
00460         }
00461         else {//empty query
00462             query = new KexiDB::SchemaData(); //just empty
00463         }
00464 
00465         (KexiDB::SchemaData&)*query = sdata; //copy main attributes
00466         ok = m_mainWin->project()->dbConnection()->storeObjectSchemaData( *query, true /*newObject*/ );
00467         if (ok) {
00468             m_dialog->setId( query->id() );
00469             ok = storeDataBlock( d->editor->text(), "sql" );
00470         }
00471     }
00472     else {
00473         //query is not ok
00474 //#if 0
00475     //TODO: allow saving invalid queries
00476     //TODO: just ask this question:
00477         query = new KexiDB::SchemaData(); //just empty
00478 
00479         ok = (KMessageBox::questionYesNo(this, i18n("Do you want to save invalid query?"),
00480             0, KStdGuiItem::yes(), KStdGuiItem::no(), "askBeforeSavingInvalidQueries"/*config entry*/)==KMessageBox::Yes);
00481         if (ok) {
00482             (KexiDB::SchemaData&)*query = sdata; //copy main attributes
00483             ok = m_mainWin->project()->dbConnection()->storeObjectSchemaData( *query, true /*newObject*/ );
00484         }
00485         if (ok) {
00486             m_dialog->setId( query->id() );
00487             ok = storeDataBlock( d->editor->text(), "sql" );
00488         }
00489 //#else
00490         //ok = false;
00491 //#endif
00492     }
00493     if (!ok) {
00494         delete query;
00495         query = 0;
00496     }
00497     return query;
00498 }
00499 
00500 tristate KexiQueryDesignerSQLView::storeData(bool dontAsk)
00501 {
00502     tristate res = KexiViewBase::storeData(dontAsk);
00503     if (~res)
00504         return res;
00505     if (res == true) {
00506         res = storeDataBlock( d->editor->text(), "sql" );
00507 #if 0
00508         bool queryOK = slotCheckQuery();
00509         if (queryOK) {
00510             res = storeDataBlock( d->editor->text(), "sql" );
00511         }
00512         else {
00513             //query is not ok
00514             //TODO: allow saving invalid queries
00515             //TODO: just ask this question:
00516             res = false;
00517         }
00518 #endif
00519     }
00520     if (res == true) {
00521         QString empty_xml;
00522         res = storeDataBlock( empty_xml, "query_layout" ); //clear
00523     }
00524     if (!res)
00525         setDirty(true);
00526     return res;
00527 }
00528 
00529 
00530 /*void KexiQueryDesignerSQLView::slotHistoryHeaderButtonClicked(const QString& buttonIdentifier)
00531 {
00532     if (buttonIdentifier=="select_query") {
00533         slotSelectQuery();
00534     }
00535     else if (buttonIdentifier=="clear_history") {
00536         d->history->clear();
00537     }
00538 }*/
00539 
00540 #include "kexiquerydesignersql.moc"
00541 
KDE Home | KDE Accessibility Home | Description of Access Keys