/* This file is part of the KDE project
Copyright (C) 2003 Lucijan Busch
Copyright (C) 2004-2006 Jaroslaw Staniek
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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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( 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(""+i18n("The query is correct")+"
");
d->history->addEvent(d->editor->text().stripWhiteSpace(), true, QString::null);
}
void KexiQueryDesignerSQLView::setStatusError(const QString& msg)
{
d->pixmapStatus->setPixmap(d->statusPixmapErr);
setStatusText(""+i18n("The query is incorrect")+"
"+msg+"
");
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 sz = d->splitter->sizes();
const int newHeight = rt.height()+d->lblStatus->margin()*2;
if (sz[1]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, ""+i18n("The query you entered is incorrect.")
+"
"+i18n("Do you want to cancel any changes made to this SQL text?")+"
"
+"
"+i18n("Answering \"No\" allows you to make corrections.")+"
"))
{
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(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 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(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"