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