kexi

kexiquerydesignersql.cpp
1/* This file is part of the KDE project
2 Copyright (C) 2003 Lucijan Busch <lucijan@kde.org>
3 Copyright (C) 2004-2006 Jaroslaw Staniek <js@iidea.pl>
4
5 This program is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Library General Public
7 License as published by the Free Software Foundation; either
8 version 2 of the License, or (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Library General Public License for more details.
14
15 You should have received a copy of the GNU Library General Public License
16 along with this program; see the file COPYING. If not, write to
17 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
19*/
20
21#include <tqsplitter.h>
22#include <tqlayout.h>
23#include <tqhbox.h>
24#include <tqvbox.h>
25#include <tqtimer.h>
26
27#include <tdeapplication.h>
28#include <kdebug.h>
29#include <tdemessagebox.h>
30#include <kiconloader.h>
31
32#include <kexiutils/utils.h>
33#include <kexidb/driver.h>
34#include <kexidb/connection.h>
35#include <kexidb/parser/parser.h>
36
37#include <kexiproject.h>
38#include <keximainwindow.h>
39
40#include "kexiquerydesignersqleditor.h"
41#include "kexiquerydesignersqlhistory.h"
42#include "kexiquerydesignersql.h"
43#include "kexiquerypart.h"
44
45#include "kexisectionheader.h"
46
47
48static bool compareSQL(const TQString& sql1, const TQString& sql2)
49{
50 //TODO: use reformatting functions here
51 return sql1.stripWhiteSpace()==sql2.stripWhiteSpace();
52}
53
54//===================
55
57class KexiQueryDesignerSQLView::Private
58{
59 public:
60 Private() :
61 history(0)
62 , historyHead(0)
63 , statusPixmapOk( DesktopIcon("button_ok") )
64 , statusPixmapErr( DesktopIcon("button_cancel") )
65 , statusPixmapInfo( DesktopIcon("messagebox_info") )
66 , parsedQuery(0)
67 , heightForStatusMode(-1)
68 , heightForHistoryMode(-1)
69 , eventFilterForSplitterEnabled(true)
70 , justSwitchedFromNoViewMode(false)
71 , slotTextChangedEnabled(true)
72 {
73 }
74 KexiQueryDesignerSQLEditor *editor;
75 KexiQueryDesignerSQLHistory *history;
76 TQLabel *pixmapStatus, *lblStatus;
77 TQHBox *status_hbox;
78 TQVBox *history_section;
79 KexiSectionHeader *head, *historyHead;
80 TQPixmap statusPixmapOk, statusPixmapErr, statusPixmapInfo;
81 TQSplitter *splitter;
82 TDEToggleAction *action_toggle_history;
85 KexiDB::QuerySchema *parsedQuery;
87 TQString origStatement;
89 int heightForStatusMode, heightForHistoryMode;
91 bool action_toggle_history_was_checked : 1;
93 bool eventFilterForSplitterEnabled : 1;
95 bool justSwitchedFromNoViewMode : 1;
97 bool slotTextChangedEnabled : 1;
98};
99
100//===================
101
102KexiQueryDesignerSQLView::KexiQueryDesignerSQLView(KexiMainWindow *mainWin, TQWidget *parent, const char *name)
103 : KexiViewBase(mainWin, parent, name)
104 , d( new Private() )
105{
106 d->splitter = new TQSplitter(this);
107 d->splitter->setOrientation(TQt::Vertical);
108 d->head = new KexiSectionHeader(i18n("SQL Query Text"),TQt::Vertical, d->splitter);
109 d->editor = new KexiQueryDesignerSQLEditor(mainWin, d->head, "sqle");
110// d->editor->installEventFilter(this);//for keys
111 connect(d->editor, TQ_SIGNAL(textChanged()), this, TQ_SLOT(slotTextChanged()));
112 addChildView(d->editor);
113 setViewWidget(d->editor);
114 d->splitter->setFocusProxy(d->editor);
115 setFocusProxy(d->editor);
116
117 d->history_section = new TQVBox(d->splitter);
118
119 d->status_hbox = new TQHBox(d->history_section);
120 d->status_hbox->installEventFilter(this);
121 d->splitter->setResizeMode(d->history_section, TQSplitter::KeepSize);
122 d->status_hbox->setSpacing(0);
123 d->pixmapStatus = new TQLabel(d->status_hbox);
124 d->pixmapStatus->setFixedWidth(d->statusPixmapOk.width()*3/2);
125 d->pixmapStatus->setAlignment(AlignHCenter | AlignTop);
126 d->pixmapStatus->setMargin(d->statusPixmapOk.width()/4);
127 d->pixmapStatus->setPaletteBackgroundColor( palette().active().color(TQColorGroup::Base) );
128
129 d->lblStatus = new TQLabel(d->status_hbox);
130 d->lblStatus->setAlignment(AlignLeft | AlignTop | WordBreak);
131 d->lblStatus->setMargin(d->statusPixmapOk.width()/4);
132 d->lblStatus->setSizePolicy( TQSizePolicy::Preferred, TQSizePolicy::Expanding );
133 d->lblStatus->resize(d->lblStatus->width(),d->statusPixmapOk.width()*3);
134 d->lblStatus->setPaletteBackgroundColor( palette().active().color(TQColorGroup::Base) );
135
136 TQHBoxLayout *b = new TQHBoxLayout(this);
137 b->addWidget(d->splitter);
138
139 plugSharedAction("querypart_check_query", this, TQ_SLOT(slotCheckQuery()));
140 plugSharedAction("querypart_view_toggle_history", this, TQ_SLOT(slotUpdateMode()));
141 d->action_toggle_history = static_cast<TDEToggleAction*>( sharedAction( "querypart_view_toggle_history" ) );
142
143 d->historyHead = new KexiSectionHeader(i18n("SQL Query History"),TQt::Vertical, d->history_section);
144 d->historyHead->installEventFilter(this);
145 d->history = new KexiQueryDesignerSQLHistory(d->historyHead, "sql_history");
146
147 static const TQString msg_back = i18n("Back to Selected Query");
148 static const TQString msg_clear = i18n("Clear History");
149 d->historyHead->addButton("select_item", msg_back, this, TQ_SLOT(slotSelectQuery()));
150 d->historyHead->addButton("edit-clear", msg_clear, d->history, TQ_SLOT(clear()));
151 d->history->popupMenu()->insertItem(SmallIcon("select_item"), msg_back, this, TQ_SLOT(slotSelectQuery()));
152 d->history->popupMenu()->insertItem(SmallIcon("edit-clear"), msg_clear, d->history, TQ_SLOT(clear()));
153 connect(d->history, TQ_SIGNAL(currentItemDoubleClicked()), this, TQ_SLOT(slotSelectQuery()));
154
155 d->heightForHistoryMode = -1; //height() / 2;
156 //d->historyHead->hide();
157 d->action_toggle_history_was_checked = !d->action_toggle_history->isChecked(); //to force update
158 slotUpdateMode();
159 slotCheckQuery();
160}
161
162KexiQueryDesignerSQLView::~KexiQueryDesignerSQLView()
163{
164 delete d;
165}
166
167KexiQueryDesignerSQLEditor *KexiQueryDesignerSQLView::editor() const
168{
169 return d->editor;
170}
171
172void KexiQueryDesignerSQLView::setStatusOk()
173{
174 d->pixmapStatus->setPixmap(d->statusPixmapOk);
175 setStatusText("<h2>"+i18n("The query is correct")+"</h2>");
176 d->history->addEvent(d->editor->text().stripWhiteSpace(), true, TQString());
177}
178
179void KexiQueryDesignerSQLView::setStatusError(const TQString& msg)
180{
181 d->pixmapStatus->setPixmap(d->statusPixmapErr);
182 setStatusText("<h2>"+i18n("The query is incorrect")+"</h2><p>"+msg+"</p>");
183 d->history->addEvent(d->editor->text().stripWhiteSpace(), false, msg);
184}
185
186void KexiQueryDesignerSQLView::setStatusEmpty()
187{
188 d->pixmapStatus->setPixmap(d->statusPixmapInfo);
189 setStatusText(i18n("Please enter your query and execute \"Check query\" function to verify it."));
190}
191
192void KexiQueryDesignerSQLView::setStatusText(const TQString& text)
193{
194 if (!d->action_toggle_history->isChecked()) {
195 TQSimpleRichText rt(text, d->lblStatus->font());
196 rt.setWidth(d->lblStatus->width());
197 TQValueList<int> sz = d->splitter->sizes();
198 const int newHeight = rt.height()+d->lblStatus->margin()*2;
199 if (sz[1]<newHeight) {
200 sz[1] = newHeight;
201 d->splitter->setSizes(sz);
202 }
203 d->lblStatus->setText(text);
204 }
205}
206
209{
210//TODO
211 dontStore = true;
212 if (mode==Kexi::DesignViewMode || mode==Kexi::DataViewMode) {
213 TQString sqlText = d->editor->text().stripWhiteSpace();
214 KexiQueryPart::TempData * temp = tempData();
215 if (sqlText.isEmpty()) {
216 //special case: empty SQL text
217 if (temp->query()) {
218 temp->queryChangedInPreviousView = true; //query changed
219 temp->setQuery(0);
220// delete temp->query; //safe?
221// temp->query = 0;
222 }
223 }
224 else {
225 const bool designViewWasVisible = parentDialog()->viewForMode(mode)!=0;
226 //should we check SQL text?
227 if (designViewWasVisible
228 && !d->justSwitchedFromNoViewMode //unchanged, but we should check SQL text
229 && compareSQL(d->origStatement, d->editor->text())) {
230 //statement unchanged! - nothing to do
231 temp->queryChangedInPreviousView = false;
232 }
233 else {
234 //yes: parse SQL text
235 if (!slotCheckQuery()) {
236 if (KMessageBox::No==KMessageBox::warningYesNo(this, "<p>"+i18n("The query you entered is incorrect.")
237 +"</p><p>"+i18n("Do you want to cancel any changes made to this SQL text?")+"</p>"
238 +"</p><p>"+i18n("Answering \"No\" allows you to make corrections.")+"</p>"))
239 {
240 return cancelled;
241 }
242 //do not change original query - it's invalid
243 temp->queryChangedInPreviousView = false;
244 //this view is no longer _just_ switched from "NoViewMode"
245 d->justSwitchedFromNoViewMode = false;
246 return true;
247 }
248 //this view is no longer _just_ switched from "NoViewMode"
249 d->justSwitchedFromNoViewMode = false;
250 //replace old query schema with new one
251 temp->setQuery( d->parsedQuery ); //this will also delete temp->query()
252// delete temp->query; //safe?
253// temp->query = d->parsedQuery;
254 d->parsedQuery = 0;
255 temp->queryChangedInPreviousView = true;
256 }
257 }
258 }
259
260 //TODO
261 /*
262 if (d->doc) {
263 KexiDB::Parser *parser = new KexiDB::Parser(mainWin()->project()->dbConnection());
264 parser->parse(getQuery());
265 d->doc->setSchema(parser->select());
266
267 if(parser->operation() == KexiDB::Parser::OP_Error)
268 {
269 d->history->addEvent(getQuery(), false, parser->error().error());
270 kdDebug() << "KexiQueryDesignerSQLView::beforeSwitchTo(): syntax error!" << endl;
271 return false;
272 }
273 delete parser;
274 }
275
276 setDirty(true);*/
277// if (parentDialog()->hasFocus())
278 d->editor->setFocus();
279 return true;
280}
281
284{
285 kdDebug() << "KexiQueryDesignerSQLView::afterSwitchFrom()" << endl;
286// if (mode==Kexi::DesignViewMode || mode==Kexi::DataViewMode) {
287 if (mode==Kexi::NoViewMode) {
288 //User opened text view _directly_.
289 //This flag is set to indicate for beforeSwitchTo() that even if text has not been changed,
290 //SQL text should be invalidated.
291 d->justSwitchedFromNoViewMode = true;
292 }
293 KexiQueryPart::TempData * temp = tempData();
294 KexiDB::QuerySchema *query = temp->query();
295 if (!query) {//try to just get saved schema, instead of temporary one
296 query = dynamic_cast<KexiDB::QuerySchema *>(parentDialog()->schemaData());
297 }
298
299 if (mode!=0/*failure only if it is switching from prev. view*/ && !query) {
300 //TODO msg
301 return false;
302 }
303
304 if (!query) {
305 //no valid query schema delivered: just load sql text, no matter if it's valid
306 if (!loadDataBlock( d->origStatement, "sql", true /*canBeEmpty*/ ))
307 return false;
308 }
309 else {
310 // Use query with Kexi keywords (but not driver-specific keywords) escaped.
311 temp->setQuery( query );
312// temp->query = query;
315 options.identifierEscaping = KexiDB::Driver::EscapeKexi;
316 options.addVisibleLookupColumns = false;
317 d->origStatement = conn->selectStatement(*query, options).stripWhiteSpace();
318 }
319
320 d->slotTextChangedEnabled = false;
321 d->editor->setText( d->origStatement );
322 d->slotTextChangedEnabled = true;
323 TQTimer::singleShot(100, d->editor, TQ_SLOT(setFocus()));
324 return true;
325}
326
327TQString
328KexiQueryDesignerSQLView::sqlText() const
329{
330 return d->editor->text();
331}
332
334{
335 TQString sqlText( d->editor->text().stripWhiteSpace() );
336 if (sqlText.isEmpty()) {
337 delete d->parsedQuery;
338 d->parsedQuery = 0;
339 setStatusEmpty();
340 return true;
341 }
342
343 kdDebug() << "KexiQueryDesignerSQLView::slotCheckQuery()" << endl;
344 //KexiQueryPart::TempData * temp = tempData();
345 KexiDB::Parser *parser = mainWin()->project()->sqlParser();
346 const bool ok = parser->parse( sqlText );
347 delete d->parsedQuery;
348 d->parsedQuery = parser->query();
349 if (!d->parsedQuery || !ok || !parser->error().type().isEmpty()) {
350 KexiDB::ParserError err = parser->error();
351 setStatusError(err.error());
352 d->editor->jump(err.at());
353 delete d->parsedQuery;
354 d->parsedQuery = 0;
355 return false;
356 }
357
358 setStatusOk();
359 return true;
360}
361
362void KexiQueryDesignerSQLView::slotUpdateMode()
363{
364 if (d->action_toggle_history->isChecked() == d->action_toggle_history_was_checked)
365 return;
366
367 d->eventFilterForSplitterEnabled = false;
368
369 TQValueList<int> sz = d->splitter->sizes();
370 d->action_toggle_history_was_checked = d->action_toggle_history->isChecked();
371 int heightToSet = -1;
372 if (d->action_toggle_history->isChecked()) {
373 d->status_hbox->hide();
374 d->historyHead->show();
375 d->history->show();
376 if (d->heightForHistoryMode==-1)
377 d->heightForHistoryMode = m_dialog->height() / 2;
378 heightToSet = d->heightForHistoryMode;
379 d->heightForStatusMode = sz[1]; //remember
380 }
381 else {
382 if (d->historyHead)
383 d->historyHead->hide();
384 d->status_hbox->show();
385 if (d->heightForStatusMode>=0) {
386 heightToSet = d->heightForStatusMode;
387 } else {
388 d->heightForStatusMode = d->status_hbox->height();
389 }
390 if (d->heightForHistoryMode>=0)
391 d->heightForHistoryMode = sz[1];
392 }
393
394 if (heightToSet>=0) {
395 sz[1] = heightToSet;
396 d->splitter->setSizes(sz);
397 }
398 d->eventFilterForSplitterEnabled = true;
400}
401
402void KexiQueryDesignerSQLView::slotTextChanged()
403{
404 if (!d->slotTextChangedEnabled)
405 return;
406 setDirty(true);
407 setStatusEmpty();
408}
409
410bool KexiQueryDesignerSQLView::eventFilter( TQObject *o, TQEvent *e )
411{
412 if (d->eventFilterForSplitterEnabled) {
413 if (e->type()==TQEvent::Resize && o && o==d->historyHead && d->historyHead->isVisible()) {
414 d->heightForHistoryMode = d->historyHead->height();
415 }
416 else if (e->type()==TQEvent::Resize && o && o==d->status_hbox && d->status_hbox->isVisible()) {
417 d->heightForStatusMode = d->status_hbox->height();
418 }
419 }
420 return KexiViewBase::eventFilter(o, e);
421}
422
424{
425 if (activated) {
426 slotUpdateMode();
427 }
428 setAvailable("querypart_check_query", true);
429 setAvailable("querypart_view_toggle_history", true);
431}
432
433void KexiQueryDesignerSQLView::slotSelectQuery()
434{
435 TQString sql = d->history->selectedStatement();
436 if (!sql.isEmpty()) {
437 d->editor->setText( sql );
438 }
439}
440
442KexiQueryDesignerSQLView::tempData() const
443{
444 return dynamic_cast<KexiQueryPart::TempData*>(parentDialog()->tempData());
445}
446
449{
450 Q_UNUSED( cancel );
451
452 //here: we won't store query layout: it will be recreated 'by hand' in GUI Query Editor
453 bool queryOK = slotCheckQuery();
454 bool ok = true;
455 KexiDB::SchemaData* query = 0;
456 if (queryOK) {
457 //query is ok
458 if (d->parsedQuery) {
459 query = d->parsedQuery; //will be returned, so: don't keep it
460 d->parsedQuery = 0;
461 }
462 else {//empty query
463 query = new KexiDB::SchemaData(); //just empty
464 }
465
466 (KexiDB::SchemaData&)*query = sdata; //copy main attributes
467 ok = m_mainWin->project()->dbConnection()->storeObjectSchemaData( *query, true /*newObject*/ );
468 if (ok) {
469 m_dialog->setId( query->id() );
470 ok = storeDataBlock( d->editor->text(), "sql" );
471 }
472 }
473 else {
474 //query is not ok
475//#if 0
476 //TODO: allow saving invalid queries
477 //TODO: just ask this question:
478 query = new KexiDB::SchemaData(); //just empty
479
480 ok = (KMessageBox::questionYesNo(this, i18n("Do you want to save invalid query?"),
481 0, KStdGuiItem::yes(), KStdGuiItem::no(), "askBeforeSavingInvalidQueries"/*config entry*/)==KMessageBox::Yes);
482 if (ok) {
483 (KexiDB::SchemaData&)*query = sdata; //copy main attributes
484 ok = m_mainWin->project()->dbConnection()->storeObjectSchemaData( *query, true /*newObject*/ );
485 }
486 if (ok) {
487 m_dialog->setId( query->id() );
488 ok = storeDataBlock( d->editor->text(), "sql" );
489 }
490//#else
491 //ok = false;
492//#endif
493 }
494 if (!ok) {
495 delete query;
496 query = 0;
497 }
498 return query;
499}
500
502{
503 tristate res = KexiViewBase::storeData(dontAsk);
504 if (~res)
505 return res;
506 if (res == true) {
507 res = storeDataBlock( d->editor->text(), "sql" );
508#if 0
509 bool queryOK = slotCheckQuery();
510 if (queryOK) {
511 res = storeDataBlock( d->editor->text(), "sql" );
512 }
513 else {
514 //query is not ok
515 //TODO: allow saving invalid queries
516 //TODO: just ask this question:
517 res = false;
518 }
519#endif
520 }
521 if (res == true) {
522 TQString empty_xml;
523 res = storeDataBlock( empty_xml, "query_layout" ); //clear
524 }
525 if (!res)
526 setDirty(true);
527 return res;
528}
529
530
531/*void KexiQueryDesignerSQLView::slotHistoryHeaderButtonClicked(const TQString& buttonIdentifier)
532{
533 if (buttonIdentifier=="select_query") {
534 slotSelectQuery();
535 }
536 else if (buttonIdentifier=="clear_history") {
537 d->history->clear();
538 }
539}*/
540
541#include "kexiquerydesignersql.moc"
542
options used in selectStatement()
Definition connection.h:672
int identifierEscaping
A mode for escaping identifier, Driver::EscapeDriver|DriverEscapeAsNecessary by default.
Definition connection.h:678
Provides database connection, allowing queries and data modification.
Definition connection.h:62
TQString selectStatement(QuerySchema &querySchema, const TQValueList< TQVariant > &params, const SelectStatementOptions &options=SelectStatementOptions()) const
Provides detailed i18n'ed error description about the Parser .
Definition parser.h:43
TQString error()
Definition parser.h:74
Parser for SQL statements.
Definition parser.h:113
bool parse(const TQString &statement)
clears previous results and runs the parser
Definition parser.cpp:116
KexiDB::QuerySchema provides information about database query.
KexiDialogTempData * tempData() const
KexiViewBase * viewForMode(int mode) const
void setText(const TQString &text)
TQString text()
Kexi's main window interface This interface is implemented by KexiMainWindowImpl class.
virtual KexiProject * project()=0
Project data of currently opened project or NULL if no project here yet.
KexiDB::Connection * dbConnection() const
Text editor for entering query statements.
virtual tristate afterSwitchFrom(int mode)
virtual void updateActions(bool activated)
virtual KexiDB::SchemaData * storeNewData(const KexiDB::SchemaData &sdata, bool &cancel)
virtual tristate storeData(bool dontAsk=false)
virtual tristate beforeSwitchTo(int mode, bool &dontStore)
Temporary data kept in memory while switching between Query Dialog's views.
KexiDB::QuerySchema * query() const
void setQuery(KexiDB::QuerySchema *query)
Base class for single view embeddable of in KexiDialogBase.
virtual tristate storeData(bool dontAsk=false)
virtual void setAvailable(const char *action_name, bool set)
bool storeDataBlock(const TQString &dataString, const TQString &dataID=TQString())
bool loadDataBlock(TQString &dataString, const TQString &dataID=TQString(), bool canBeEmpty=false)
KexiDialogBase * parentDialog() const
KexiMainWindow * mainWin() const
virtual void updateActions(bool activated)
3-state logical type with three values: true, false and cancelled and convenient operators.
Definition tristate.h:100
@ NoViewMode
Definition kexi.h:44