summaryrefslogtreecommitdiffstats
path: root/kexi/plugins/queries/kexiquerydesignersql.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'kexi/plugins/queries/kexiquerydesignersql.cpp')
-rw-r--r--kexi/plugins/queries/kexiquerydesignersql.cpp542
1 files changed, 542 insertions, 0 deletions
diff --git a/kexi/plugins/queries/kexiquerydesignersql.cpp b/kexi/plugins/queries/kexiquerydesignersql.cpp
new file mode 100644
index 00000000..469d551c
--- /dev/null
+++ b/kexi/plugins/queries/kexiquerydesignersql.cpp
@@ -0,0 +1,542 @@
+/* This file is part of the KDE project
+ Copyright (C) 2003 Lucijan Busch <[email protected]>
+ Copyright (C) 2004-2006 Jaroslaw Staniek <[email protected]>
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <qsplitter.h>
+#include <qlayout.h>
+#include <qhbox.h>
+#include <qvbox.h>
+#include <qtimer.h>
+
+#include <kapplication.h>
+#include <kdebug.h>
+#include <kmessagebox.h>
+#include <kiconloader.h>
+
+#include <kexiutils/utils.h>
+#include <kexidb/driver.h>
+#include <kexidb/connection.h>
+#include <kexidb/parser/parser.h>
+
+#include <kexiproject.h>
+#include <keximainwindow.h>
+
+#include "kexiquerydesignersqleditor.h"
+#include "kexiquerydesignersqlhistory.h"
+#include "kexiquerydesignersql.h"
+#include "kexiquerypart.h"
+
+#include "kexisectionheader.h"
+
+
+static bool compareSQL(const QString& sql1, const QString& sql2)
+{
+ //TODO: use reformatting functions here
+ return sql1.stripWhiteSpace()==sql2.stripWhiteSpace();
+}
+
+//===================
+
+//! @internal
+class KexiQueryDesignerSQLView::Private
+{
+ public:
+ Private() :
+ history(0)
+ , historyHead(0)
+ , statusPixmapOk( DesktopIcon("button_ok") )
+ , statusPixmapErr( DesktopIcon("button_cancel") )
+ , statusPixmapInfo( DesktopIcon("messagebox_info") )
+ , parsedQuery(0)
+ , heightForStatusMode(-1)
+ , heightForHistoryMode(-1)
+ , eventFilterForSplitterEnabled(true)
+ , justSwitchedFromNoViewMode(false)
+ , slotTextChangedEnabled(true)
+ {
+ }
+ KexiQueryDesignerSQLEditor *editor;
+ KexiQueryDesignerSQLHistory *history;
+ QLabel *pixmapStatus, *lblStatus;
+ QHBox *status_hbox;
+ QVBox *history_section;
+ KexiSectionHeader *head, *historyHead;
+ QPixmap statusPixmapOk, statusPixmapErr, statusPixmapInfo;
+ QSplitter *splitter;
+ KToggleAction *action_toggle_history;
+ //! For internal use, this pointer is usually copied to TempData structure,
+ //! when switching out of this view (then it's cleared).
+ KexiDB::QuerySchema *parsedQuery;
+ //! For internal use, statement passed in switching to this view
+ QString origStatement;
+ //! needed to remember height for both modes, between switching
+ int heightForStatusMode, heightForHistoryMode;
+ //! helper for slotUpdateMode()
+ bool action_toggle_history_was_checked : 1;
+ //! helper for eventFilter()
+ bool eventFilterForSplitterEnabled : 1;
+ //! helper for beforeSwitchTo()
+ bool justSwitchedFromNoViewMode : 1;
+ //! helper for slotTextChanged()
+ bool slotTextChangedEnabled : 1;
+};
+
+//===================
+
+KexiQueryDesignerSQLView::KexiQueryDesignerSQLView(KexiMainWindow *mainWin, QWidget *parent, const char *name)
+ : KexiViewBase(mainWin, parent, name)
+ , d( new Private() )
+{
+ d->splitter = new QSplitter(this);
+ d->splitter->setOrientation(Vertical);
+ d->head = new KexiSectionHeader(i18n("SQL Query Text"), Vertical, d->splitter);
+ d->editor = new KexiQueryDesignerSQLEditor(mainWin, d->head, "sqle");
+// d->editor->installEventFilter(this);//for keys
+ connect(d->editor, SIGNAL(textChanged()), this, SLOT(slotTextChanged()));
+ addChildView(d->editor);
+ setViewWidget(d->editor);
+ d->splitter->setFocusProxy(d->editor);
+ setFocusProxy(d->editor);
+
+ d->history_section = new QVBox(d->splitter);
+
+ d->status_hbox = new QHBox(d->history_section);
+ d->status_hbox->installEventFilter(this);
+ d->splitter->setResizeMode(d->history_section, QSplitter::KeepSize);
+ d->status_hbox->setSpacing(0);
+ d->pixmapStatus = new QLabel(d->status_hbox);
+ d->pixmapStatus->setFixedWidth(d->statusPixmapOk.width()*3/2);
+ d->pixmapStatus->setAlignment(AlignHCenter | AlignTop);
+ d->pixmapStatus->setMargin(d->statusPixmapOk.width()/4);
+ d->pixmapStatus->setPaletteBackgroundColor( palette().active().color(QColorGroup::Base) );
+
+ d->lblStatus = new QLabel(d->status_hbox);
+ d->lblStatus->setAlignment(AlignLeft | AlignTop | WordBreak);
+ d->lblStatus->setMargin(d->statusPixmapOk.width()/4);
+ d->lblStatus->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Expanding );
+ d->lblStatus->resize(d->lblStatus->width(),d->statusPixmapOk.width()*3);
+ d->lblStatus->setPaletteBackgroundColor( palette().active().color(QColorGroup::Base) );
+
+ QHBoxLayout *b = new QHBoxLayout(this);
+ b->addWidget(d->splitter);
+
+ plugSharedAction("querypart_check_query", this, SLOT(slotCheckQuery()));
+ plugSharedAction("querypart_view_toggle_history", this, SLOT(slotUpdateMode()));
+ d->action_toggle_history = static_cast<KToggleAction*>( sharedAction( "querypart_view_toggle_history" ) );
+
+ d->historyHead = new KexiSectionHeader(i18n("SQL Query History"), Vertical, d->history_section);
+ d->historyHead->installEventFilter(this);
+ d->history = new KexiQueryDesignerSQLHistory(d->historyHead, "sql_history");
+
+ static const QString msg_back = i18n("Back to Selected Query");
+ static const QString msg_clear = i18n("Clear History");
+ d->historyHead->addButton("select_item", msg_back, this, SLOT(slotSelectQuery()));
+ d->historyHead->addButton("editclear", msg_clear, d->history, SLOT(clear()));
+ d->history->popupMenu()->insertItem(SmallIcon("select_item"), msg_back, this, SLOT(slotSelectQuery()));
+ d->history->popupMenu()->insertItem(SmallIcon("editclear"), msg_clear, d->history, SLOT(clear()));
+ connect(d->history, SIGNAL(currentItemDoubleClicked()), this, SLOT(slotSelectQuery()));
+
+ d->heightForHistoryMode = -1; //height() / 2;
+ //d->historyHead->hide();
+ d->action_toggle_history_was_checked = !d->action_toggle_history->isChecked(); //to force update
+ slotUpdateMode();
+ slotCheckQuery();
+}
+
+KexiQueryDesignerSQLView::~KexiQueryDesignerSQLView()
+{
+ delete d;
+}
+
+KexiQueryDesignerSQLEditor *KexiQueryDesignerSQLView::editor() const
+{
+ return d->editor;
+}
+
+void KexiQueryDesignerSQLView::setStatusOk()
+{
+ d->pixmapStatus->setPixmap(d->statusPixmapOk);
+ setStatusText("<h2>"+i18n("The query is correct")+"</h2>");
+ d->history->addEvent(d->editor->text().stripWhiteSpace(), true, QString::null);
+}
+
+void KexiQueryDesignerSQLView::setStatusError(const QString& msg)
+{
+ d->pixmapStatus->setPixmap(d->statusPixmapErr);
+ setStatusText("<h2>"+i18n("The query is incorrect")+"</h2><p>"+msg+"</p>");
+ d->history->addEvent(d->editor->text().stripWhiteSpace(), false, msg);
+}
+
+void KexiQueryDesignerSQLView::setStatusEmpty()
+{
+ d->pixmapStatus->setPixmap(d->statusPixmapInfo);
+ setStatusText(i18n("Please enter your query and execute \"Check query\" function to verify it."));
+}
+
+void KexiQueryDesignerSQLView::setStatusText(const QString& text)
+{
+ if (!d->action_toggle_history->isChecked()) {
+ QSimpleRichText rt(text, d->lblStatus->font());
+ rt.setWidth(d->lblStatus->width());
+ QValueList<int> sz = d->splitter->sizes();
+ const int newHeight = rt.height()+d->lblStatus->margin()*2;
+ if (sz[1]<newHeight) {
+ sz[1] = newHeight;
+ d->splitter->setSizes(sz);
+ }
+ d->lblStatus->setText(text);
+ }
+}
+
+tristate
+KexiQueryDesignerSQLView::beforeSwitchTo(int mode, bool &dontStore)
+{
+//TODO
+ dontStore = true;
+ if (mode==Kexi::DesignViewMode || mode==Kexi::DataViewMode) {
+ QString sqlText = d->editor->text().stripWhiteSpace();
+ KexiQueryPart::TempData * temp = tempData();
+ if (sqlText.isEmpty()) {
+ //special case: empty SQL text
+ if (temp->query()) {
+ temp->queryChangedInPreviousView = true; //query changed
+ temp->setQuery(0);
+// delete temp->query; //safe?
+// temp->query = 0;
+ }
+ }
+ else {
+ const bool designViewWasVisible = parentDialog()->viewForMode(mode)!=0;
+ //should we check SQL text?
+ if (designViewWasVisible
+ && !d->justSwitchedFromNoViewMode //unchanged, but we should check SQL text
+ && compareSQL(d->origStatement, d->editor->text())) {
+ //statement unchanged! - nothing to do
+ temp->queryChangedInPreviousView = false;
+ }
+ else {
+ //yes: parse SQL text
+ if (!slotCheckQuery()) {
+ if (KMessageBox::No==KMessageBox::warningYesNo(this, "<p>"+i18n("The query you entered is incorrect.")
+ +"</p><p>"+i18n("Do you want to cancel any changes made to this SQL text?")+"</p>"
+ +"</p><p>"+i18n("Answering \"No\" allows you to make corrections.")+"</p>"))
+ {
+ return cancelled;
+ }
+ //do not change original query - it's invalid
+ temp->queryChangedInPreviousView = false;
+ //this view is no longer _just_ switched from "NoViewMode"
+ d->justSwitchedFromNoViewMode = false;
+ return true;
+ }
+ //this view is no longer _just_ switched from "NoViewMode"
+ d->justSwitchedFromNoViewMode = false;
+ //replace old query schema with new one
+ temp->setQuery( d->parsedQuery ); //this will also delete temp->query()
+// delete temp->query; //safe?
+// temp->query = d->parsedQuery;
+ d->parsedQuery = 0;
+ temp->queryChangedInPreviousView = true;
+ }
+ }
+ }
+
+ //TODO
+ /*
+ if (d->doc) {
+ KexiDB::Parser *parser = new KexiDB::Parser(mainWin()->project()->dbConnection());
+ parser->parse(getQuery());
+ d->doc->setSchema(parser->select());
+
+ if(parser->operation() == KexiDB::Parser::OP_Error)
+ {
+ d->history->addEvent(getQuery(), false, parser->error().error());
+ kdDebug() << "KexiQueryDesignerSQLView::beforeSwitchTo(): syntax error!" << endl;
+ return false;
+ }
+ delete parser;
+ }
+
+ setDirty(true);*/
+// if (parentDialog()->hasFocus())
+ d->editor->setFocus();
+ return true;
+}
+
+tristate
+KexiQueryDesignerSQLView::afterSwitchFrom(int mode)
+{
+ kdDebug() << "KexiQueryDesignerSQLView::afterSwitchFrom()" << endl;
+// if (mode==Kexi::DesignViewMode || mode==Kexi::DataViewMode) {
+ if (mode==Kexi::NoViewMode) {
+ //User opened text view _directly_.
+ //This flag is set to indicate for beforeSwitchTo() that even if text has not been changed,
+ //SQL text should be invalidated.
+ d->justSwitchedFromNoViewMode = true;
+ }
+ KexiQueryPart::TempData * temp = tempData();
+ KexiDB::QuerySchema *query = temp->query();
+ if (!query) {//try to just get saved schema, instead of temporary one
+ query = dynamic_cast<KexiDB::QuerySchema *>(parentDialog()->schemaData());
+ }
+
+ if (mode!=0/*failure only if it is switching from prev. view*/ && !query) {
+ //TODO msg
+ return false;
+ }
+
+ if (!query) {
+ //no valid query schema delivered: just load sql text, no matter if it's valid
+ if (!loadDataBlock( d->origStatement, "sql", true /*canBeEmpty*/ ))
+ return false;
+ }
+ else {
+ // Use query with Kexi keywords (but not driver-specific keywords) escaped.
+ temp->setQuery( query );
+// temp->query = query;
+ KexiDB::Connection* conn = mainWin()->project()->dbConnection();
+ KexiDB::Connection::SelectStatementOptions options;
+ options.identifierEscaping = KexiDB::Driver::EscapeKexi;
+ options.addVisibleLookupColumns = false;
+ d->origStatement = conn->selectStatement(*query, options).stripWhiteSpace();
+ }
+
+ d->slotTextChangedEnabled = false;
+ d->editor->setText( d->origStatement );
+ d->slotTextChangedEnabled = true;
+ QTimer::singleShot(100, d->editor, SLOT(setFocus()));
+ return true;
+}
+
+QString
+KexiQueryDesignerSQLView::sqlText() const
+{
+ return d->editor->text();
+}
+
+bool KexiQueryDesignerSQLView::slotCheckQuery()
+{
+ QString sqlText( d->editor->text().stripWhiteSpace() );
+ if (sqlText.isEmpty()) {
+ delete d->parsedQuery;
+ d->parsedQuery = 0;
+ setStatusEmpty();
+ return true;
+ }
+
+ kdDebug() << "KexiQueryDesignerSQLView::slotCheckQuery()" << endl;
+ //KexiQueryPart::TempData * temp = tempData();
+ KexiDB::Parser *parser = mainWin()->project()->sqlParser();
+ const bool ok = parser->parse( sqlText );
+ delete d->parsedQuery;
+ d->parsedQuery = parser->query();
+ if (!d->parsedQuery || !ok || !parser->error().type().isEmpty()) {
+ KexiDB::ParserError err = parser->error();
+ setStatusError(err.error());
+ d->editor->jump(err.at());
+ delete d->parsedQuery;
+ d->parsedQuery = 0;
+ return false;
+ }
+
+ setStatusOk();
+ return true;
+}
+
+void KexiQueryDesignerSQLView::slotUpdateMode()
+{
+ if (d->action_toggle_history->isChecked() == d->action_toggle_history_was_checked)
+ return;
+
+ d->eventFilterForSplitterEnabled = false;
+
+ QValueList<int> sz = d->splitter->sizes();
+ d->action_toggle_history_was_checked = d->action_toggle_history->isChecked();
+ int heightToSet = -1;
+ if (d->action_toggle_history->isChecked()) {
+ d->status_hbox->hide();
+ d->historyHead->show();
+ d->history->show();
+ if (d->heightForHistoryMode==-1)
+ d->heightForHistoryMode = m_dialog->height() / 2;
+ heightToSet = d->heightForHistoryMode;
+ d->heightForStatusMode = sz[1]; //remember
+ }
+ else {
+ if (d->historyHead)
+ d->historyHead->hide();
+ d->status_hbox->show();
+ if (d->heightForStatusMode>=0) {
+ heightToSet = d->heightForStatusMode;
+ } else {
+ d->heightForStatusMode = d->status_hbox->height();
+ }
+ if (d->heightForHistoryMode>=0)
+ d->heightForHistoryMode = sz[1];
+ }
+
+ if (heightToSet>=0) {
+ sz[1] = heightToSet;
+ d->splitter->setSizes(sz);
+ }
+ d->eventFilterForSplitterEnabled = true;
+ slotCheckQuery();
+}
+
+void KexiQueryDesignerSQLView::slotTextChanged()
+{
+ if (!d->slotTextChangedEnabled)
+ return;
+ setDirty(true);
+ setStatusEmpty();
+}
+
+bool KexiQueryDesignerSQLView::eventFilter( QObject *o, QEvent *e )
+{
+ if (d->eventFilterForSplitterEnabled) {
+ if (e->type()==QEvent::Resize && o && o==d->historyHead && d->historyHead->isVisible()) {
+ d->heightForHistoryMode = d->historyHead->height();
+ }
+ else if (e->type()==QEvent::Resize && o && o==d->status_hbox && d->status_hbox->isVisible()) {
+ d->heightForStatusMode = d->status_hbox->height();
+ }
+ }
+ return KexiViewBase::eventFilter(o, e);
+}
+
+void KexiQueryDesignerSQLView::updateActions(bool activated)
+{
+ if (activated) {
+ slotUpdateMode();
+ }
+ setAvailable("querypart_check_query", true);
+ setAvailable("querypart_view_toggle_history", true);
+ KexiViewBase::updateActions(activated);
+}
+
+void KexiQueryDesignerSQLView::slotSelectQuery()
+{
+ QString sql = d->history->selectedStatement();
+ if (!sql.isEmpty()) {
+ d->editor->setText( sql );
+ }
+}
+
+KexiQueryPart::TempData *
+KexiQueryDesignerSQLView::tempData() const
+{
+ return dynamic_cast<KexiQueryPart::TempData*>(parentDialog()->tempData());
+}
+
+KexiDB::SchemaData*
+KexiQueryDesignerSQLView::storeNewData(const KexiDB::SchemaData& sdata, bool &cancel)
+{
+ Q_UNUSED( cancel );
+
+ //here: we won't store query layout: it will be recreated 'by hand' in GUI Query Editor
+ bool queryOK = slotCheckQuery();
+ bool ok = true;
+ KexiDB::SchemaData* query = 0;
+ if (queryOK) {
+ //query is ok
+ if (d->parsedQuery) {
+ query = d->parsedQuery; //will be returned, so: don't keep it
+ d->parsedQuery = 0;
+ }
+ else {//empty query
+ query = new KexiDB::SchemaData(); //just empty
+ }
+
+ (KexiDB::SchemaData&)*query = sdata; //copy main attributes
+ ok = m_mainWin->project()->dbConnection()->storeObjectSchemaData( *query, true /*newObject*/ );
+ if (ok) {
+ m_dialog->setId( query->id() );
+ ok = storeDataBlock( d->editor->text(), "sql" );
+ }
+ }
+ else {
+ //query is not ok
+//#if 0
+ //TODO: allow saving invalid queries
+ //TODO: just ask this question:
+ query = new KexiDB::SchemaData(); //just empty
+
+ ok = (KMessageBox::questionYesNo(this, i18n("Do you want to save invalid query?"),
+ 0, KStdGuiItem::yes(), KStdGuiItem::no(), "askBeforeSavingInvalidQueries"/*config entry*/)==KMessageBox::Yes);
+ if (ok) {
+ (KexiDB::SchemaData&)*query = sdata; //copy main attributes
+ ok = m_mainWin->project()->dbConnection()->storeObjectSchemaData( *query, true /*newObject*/ );
+ }
+ if (ok) {
+ m_dialog->setId( query->id() );
+ ok = storeDataBlock( d->editor->text(), "sql" );
+ }
+//#else
+ //ok = false;
+//#endif
+ }
+ if (!ok) {
+ delete query;
+ query = 0;
+ }
+ return query;
+}
+
+tristate KexiQueryDesignerSQLView::storeData(bool dontAsk)
+{
+ tristate res = KexiViewBase::storeData(dontAsk);
+ if (~res)
+ return res;
+ if (res == true) {
+ res = storeDataBlock( d->editor->text(), "sql" );
+#if 0
+ bool queryOK = slotCheckQuery();
+ if (queryOK) {
+ res = storeDataBlock( d->editor->text(), "sql" );
+ }
+ else {
+ //query is not ok
+ //TODO: allow saving invalid queries
+ //TODO: just ask this question:
+ res = false;
+ }
+#endif
+ }
+ if (res == true) {
+ QString empty_xml;
+ res = storeDataBlock( empty_xml, "query_layout" ); //clear
+ }
+ if (!res)
+ setDirty(true);
+ return res;
+}
+
+
+/*void KexiQueryDesignerSQLView::slotHistoryHeaderButtonClicked(const QString& buttonIdentifier)
+{
+ if (buttonIdentifier=="select_query") {
+ slotSelectQuery();
+ }
+ else if (buttonIdentifier=="clear_history") {
+ d->history->clear();
+ }
+}*/
+
+#include "kexiquerydesignersql.moc"
+