You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
543 lines
17 KiB
543 lines
17 KiB
/* This file is part of the KDE project
|
|
Copyright (C) 2003 Lucijan Busch <lucijan@kde.org>
|
|
Copyright (C) 2004-2006 Jaroslaw Staniek <js@iidea.pl>
|
|
|
|
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 <tqsplitter.h>
|
|
#include <tqlayout.h>
|
|
#include <tqhbox.h>
|
|
#include <tqvbox.h>
|
|
#include <tqtimer.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 TQString& sql1, const TQString& 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;
|
|
TQLabel *pixmaptqStatus, *lbltqStatus;
|
|
TQHBox *status_hbox;
|
|
TQVBox *history_section;
|
|
KexiSectionHeader *head, *historyHead;
|
|
TQPixmap statusPixmapOk, statusPixmapErr, statusPixmapInfo;
|
|
TQSplitter *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
|
|
TQString 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, TQWidget *tqparent, const char *name)
|
|
: KexiViewBase(mainWin, tqparent, name)
|
|
, d( new Private() )
|
|
{
|
|
d->splitter = new TQSplitter(this);
|
|
d->splitter->setOrientation(Qt::Vertical);
|
|
d->head = new KexiSectionHeader(i18n("SQL Query Text"),Qt::Vertical, d->splitter);
|
|
d->editor = new KexiQueryDesignerSQLEditor(mainWin, d->head, "sqle");
|
|
// d->editor->installEventFilter(this);//for keys
|
|
connect(d->editor, TQT_SIGNAL(textChanged()), TQT_TQOBJECT(this), TQT_SLOT(slotTextChanged()));
|
|
addChildView(d->editor);
|
|
setViewWidget(d->editor);
|
|
d->splitter->setFocusProxy(d->editor);
|
|
setFocusProxy(d->editor);
|
|
|
|
d->history_section = new TQVBox(d->splitter);
|
|
|
|
d->status_hbox = new TQHBox(d->history_section);
|
|
d->status_hbox->installEventFilter(this);
|
|
d->splitter->setResizeMode(d->history_section, TQSplitter::KeepSize);
|
|
d->status_hbox->setSpacing(0);
|
|
d->pixmaptqStatus = new TQLabel(d->status_hbox);
|
|
d->pixmaptqStatus->setFixedWidth(d->statusPixmapOk.width()*3/2);
|
|
d->pixmaptqStatus->tqsetAlignment(AlignHCenter | AlignTop);
|
|
d->pixmaptqStatus->setMargin(d->statusPixmapOk.width()/4);
|
|
d->pixmaptqStatus->setPaletteBackgroundColor( tqpalette().active().color(TQColorGroup::Base) );
|
|
|
|
d->lbltqStatus = new TQLabel(d->status_hbox);
|
|
d->lbltqStatus->tqsetAlignment(AlignLeft | AlignTop | WordBreak);
|
|
d->lbltqStatus->setMargin(d->statusPixmapOk.width()/4);
|
|
d->lbltqStatus->tqsetSizePolicy( TQSizePolicy::Preferred, TQSizePolicy::Expanding );
|
|
d->lbltqStatus->resize(d->lbltqStatus->width(),d->statusPixmapOk.width()*3);
|
|
d->lbltqStatus->setPaletteBackgroundColor( tqpalette().active().color(TQColorGroup::Base) );
|
|
|
|
TQHBoxLayout *b = new TQHBoxLayout(this);
|
|
b->addWidget(d->splitter);
|
|
|
|
plugSharedAction("querypart_check_query", TQT_TQOBJECT(this), TQT_SLOT(slotCheckQuery()));
|
|
plugSharedAction("querypart_view_toggle_history", TQT_TQOBJECT(this), TQT_SLOT(slotUpdateMode()));
|
|
d->action_toggle_history = static_cast<KToggleAction*>( sharedAction( "querypart_view_toggle_history" ) );
|
|
|
|
d->historyHead = new KexiSectionHeader(i18n("SQL Query History"),Qt::Vertical, d->history_section);
|
|
d->historyHead->installEventFilter(this);
|
|
d->history = new KexiQueryDesignerSQLHistory(d->historyHead, "sql_history");
|
|
|
|
static const TQString msg_back = i18n("Back to Selected Query");
|
|
static const TQString msg_clear = i18n("Clear History");
|
|
d->historyHead->addButton("select_item", msg_back, TQT_TQOBJECT(this), TQT_SLOT(slotSelectQuery()));
|
|
d->historyHead->addButton("editclear", msg_clear, TQT_TQOBJECT(d->history), TQT_SLOT(clear()));
|
|
d->history->popupMenu()->insertItem(SmallIcon("select_item"), msg_back, TQT_TQOBJECT(this), TQT_SLOT(slotSelectQuery()));
|
|
d->history->popupMenu()->insertItem(SmallIcon("editclear"), msg_clear, d->history, TQT_SLOT(clear()));
|
|
connect(d->history, TQT_SIGNAL(currentItemDoubleClicked()), TQT_TQOBJECT(this), TQT_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->pixmaptqStatus->setPixmap(d->statusPixmapOk);
|
|
setStatusText("<h2>"+i18n("The query is correct")+"</h2>");
|
|
d->history->addEvent(d->editor->text().stripWhiteSpace(), true, TQString());
|
|
}
|
|
|
|
void KexiQueryDesignerSQLView::setStatusError(const TQString& msg)
|
|
{
|
|
d->pixmaptqStatus->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->pixmaptqStatus->setPixmap(d->statusPixmapInfo);
|
|
setStatusText(i18n("Please enter your query and execute \"Check query\" function to verify it."));
|
|
}
|
|
|
|
void KexiQueryDesignerSQLView::setStatusText(const TQString& text)
|
|
{
|
|
if (!d->action_toggle_history->isChecked()) {
|
|
TQSimpleRichText rt(text, d->lbltqStatus->font());
|
|
rt.setWidth(d->lbltqStatus->width());
|
|
TQValueList<int> sz = d->splitter->sizes();
|
|
const int newHeight = rt.height()+d->lbltqStatus->margin()*2;
|
|
if (sz[1]<newHeight) {
|
|
sz[1] = newHeight;
|
|
d->splitter->setSizes(sz);
|
|
}
|
|
d->lbltqStatus->setText(text);
|
|
}
|
|
}
|
|
|
|
tristate
|
|
KexiQueryDesignerSQLView::beforeSwitchTo(int mode, bool &dontStore)
|
|
{
|
|
//TODO
|
|
dontStore = true;
|
|
if (mode==Kexi::DesignViewMode || mode==Kexi::DataViewMode) {
|
|
TQString 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;
|
|
TQTimer::singleShot(100, d->editor, TQT_SLOT(setFocus()));
|
|
return true;
|
|
}
|
|
|
|
TQString
|
|
KexiQueryDesignerSQLView::sqlText() const
|
|
{
|
|
return d->editor->text();
|
|
}
|
|
|
|
bool KexiQueryDesignerSQLView::slotCheckQuery()
|
|
{
|
|
TQString 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;
|
|
|
|
TQValueList<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( TQObject *o, TQEvent *e )
|
|
{
|
|
if (d->eventFilterForSplitterEnabled) {
|
|
if (e->type()==TQEvent::Resize && o && TQT_BASE_OBJECT(o)==TQT_BASE_OBJECT(d->historyHead) && d->historyHead->isVisible()) {
|
|
d->heightForHistoryMode = d->historyHead->height();
|
|
}
|
|
else if (e->type()==TQEvent::Resize && o && TQT_BASE_OBJECT(o)==TQT_BASE_OBJECT(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()
|
|
{
|
|
TQString 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 tqlayout: 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) {
|
|
TQString empty_xml;
|
|
res = storeDataBlock( empty_xml, "query_layout" ); //clear
|
|
}
|
|
if (!res)
|
|
setDirty(true);
|
|
return res;
|
|
}
|
|
|
|
|
|
/*void KexiQueryDesignerSQLView::slotHistoryHeaderButtonClicked(const TQString& buttonIdentifier)
|
|
{
|
|
if (buttonIdentifier=="select_query") {
|
|
slotSelectQuery();
|
|
}
|
|
else if (buttonIdentifier=="clear_history") {
|
|
d->history->clear();
|
|
}
|
|
}*/
|
|
|
|
#include "kexiquerydesignersql.moc"
|
|
|