diff options
author | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-01-20 01:29:50 +0000 |
---|---|---|
committer | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-01-20 01:29:50 +0000 |
commit | 8362bf63dea22bbf6736609b0f49c152f975eb63 (patch) | |
tree | 0eea3928e39e50fae91d4e68b21b1e6cbae25604 /kexi/plugins/queries | |
download | koffice-8362bf63dea22bbf6736609b0f49c152f975eb63.tar.gz koffice-8362bf63dea22bbf6736609b0f49c152f975eb63.zip |
Added old abandoned KDE3 version of koffice
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/applications/koffice@1077364 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'kexi/plugins/queries')
20 files changed, 4307 insertions, 0 deletions
diff --git a/kexi/plugins/queries/Makefile.am b/kexi/plugins/queries/Makefile.am new file mode 100644 index 00000000..c0b620d4 --- /dev/null +++ b/kexi/plugins/queries/Makefile.am @@ -0,0 +1,29 @@ +include $(top_srcdir)/kexi/Makefile.global + +kde_module_LTLIBRARIES = kexihandler_query.la + +kexihandler_query_la_SOURCES = kexiquerypart.cpp kexiquerydesignersql.cpp \ + kexiquerydesignersqlhistory.cpp kexiquerydesignerguieditor.cpp \ + kexiqueryview.cpp +kexihandler_query_la_LDFLAGS = $(all_libraries) $(KDE_PLUGIN) $(VER_INFO) -module -no-undefined +kexihandler_query_la_LIBADD = ../../core/libkexicore.la \ + $(top_builddir)/kexi/kexidb/libkexidb.la \ + $(top_builddir)/kexi/widget/libkexiextendedwidgets.la \ + $(top_builddir)/kexi/widget/tableview/libkexidatatable.la \ + $(top_builddir)/kexi/widget/relations/libkexirelationsview.la \ + $(top_builddir)/lib/koproperty/libkoproperty.la + +INCLUDES= -I$(top_srcdir)/kexi/core -I$(top_srcdir)/kexi \ + -I$(top_srcdir)/kexi/widget -I$(top_srcdir)/kexi/widget/tableview \ + -I$(top_srcdir)/lib -I$(top_srcdir)/lib/kofficecore \ + -I$(top_srcdir)/kexi/kexidb $(all_includes) + +servicesdir=$(kde_servicesdir)/kexi +services_DATA=kexiqueryhandler.desktop + +rcdir = $(kde_datadir)/kexi +rc_DATA = kexiquerypartui.rc kexiquerypartinstui.rc + +METASOURCES = AUTO + +include ../Makefile.common diff --git a/kexi/plugins/queries/kexiaddparamdialog.cpp b/kexi/plugins/queries/kexiaddparamdialog.cpp new file mode 100644 index 00000000..fb40f9a2 --- /dev/null +++ b/kexi/plugins/queries/kexiaddparamdialog.cpp @@ -0,0 +1,47 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Joseph Wenninger <jowenn@kde.org> + + 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 <klocale.h> +#include <kcombobox.h> +#include <klineedit.h> +#include <qvbox.h> +#include <kexidataprovider.h> +#include "kexiaddparamdialog.h" +#include "kexiaddparamdialog.moc" +#include "kexiaddparamwidget.h" + +KexiAddParamDialog::KexiAddParamDialog(QWidget *parent) + : KDialogBase(parent, "kexiaddparamdialog", true, i18n("Add Parameter"), KDialogBase::Ok|KDialogBase::Cancel, KDialogBase::Ok, true) +{ + m_wid=new KexiAddParamWidget(makeVBoxMainWidget()); + for (int i=1;i<=KexiDataProvider::Parameter::maxType;i++) + m_wid->typecombo->insertItem(KexiDataProvider::Parameter::typeDescription[i]); +} + +KexiAddParamDialog::~KexiAddParamDialog() +{ +} + +QString KexiAddParamDialog::parameterName() { + return m_wid->paramname->text(); +} + +int KexiAddParamDialog::parameterType() { + return m_wid->typecombo->currentItem()+1; +} diff --git a/kexi/plugins/queries/kexiaddparamdialog.h b/kexi/plugins/queries/kexiaddparamdialog.h new file mode 100644 index 00000000..79558a7c --- /dev/null +++ b/kexi/plugins/queries/kexiaddparamdialog.h @@ -0,0 +1,40 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Joseph Wenninger <jowenn@kde.org> + + 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. + */ + +#ifndef KEXIADDPARAMDIALOG_H +#define KEXIADDPARAMDIALOG_H + +#include <kdialogbase.h> + +class KexiAddParamWidget; + +class KexiAddParamDialog : public KDialogBase +{ + Q_OBJECT + + public: + KexiAddParamDialog(QWidget *parent); + virtual ~KexiAddParamDialog(); + QString parameterName(); + int parameterType(); + private: + KexiAddParamWidget *m_wid; +}; + +#endif diff --git a/kexi/plugins/queries/kexiaddparamwidget.ui b/kexi/plugins/queries/kexiaddparamwidget.ui new file mode 100644 index 00000000..43ec25f1 --- /dev/null +++ b/kexi/plugins/queries/kexiaddparamwidget.ui @@ -0,0 +1,135 @@ +<!DOCTYPE UI><UI version="3.1" stdsetdef="1"> +<class>KexiAddParamWidget</class> +<widget class="QWidget"> + <property name="name"> + <cstring>KexiAddParamWidget</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>496</width> + <height>205</height> + </rect> + </property> + <property name="caption"> + <string>Parameter</string> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLayoutWidget" row="0" column="0"> + <property name="name"> + <cstring>layout3</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>textLabel1</cstring> + </property> + <property name="text"> + <string>Name:</string> + </property> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout1</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>textLabel1_2</cstring> + </property> + <property name="text"> + <string>kexi_</string> + </property> + <property name="alignment"> + <set>AlignVCenter|AlignRight</set> + </property> + </widget> + <widget class="KLineEdit"> + <property name="name"> + <cstring>paramname</cstring> + </property> + </widget> + </hbox> + </widget> + </vbox> + </widget> + <spacer row="3" column="0"> + <property name="name"> + <cstring>spacer1</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + <widget class="QLayoutWidget" row="2" column="0"> + <property name="name"> + <cstring>layout4</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>textLabel1_4</cstring> + </property> + <property name="text"> + <string>Message:</string> + </property> + </widget> + <widget class="QLineEdit"> + <property name="name"> + <cstring>message</cstring> + </property> + </widget> + </vbox> + </widget> + <widget class="QLayoutWidget" row="1" column="0"> + <property name="name"> + <cstring>layout5</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>textLabel1_3</cstring> + </property> + <property name="text"> + <string>Type:</string> + </property> + </widget> + <widget class="QComboBox"> + <property name="name"> + <cstring>typecombo</cstring> + </property> + </widget> + </vbox> + </widget> + </grid> +</widget> +<layoutdefaults spacing="6" margin="11"/> +<includehints> + <includehint>klineedit.h</includehint> +</includehints> +</UI> diff --git a/kexi/plugins/queries/kexidynamicqueryparameterdialog.cpp b/kexi/plugins/queries/kexidynamicqueryparameterdialog.cpp new file mode 100644 index 00000000..4a77f37c --- /dev/null +++ b/kexi/plugins/queries/kexidynamicqueryparameterdialog.cpp @@ -0,0 +1,63 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Joseph Wenninger <jowenn@kde.org> + + 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 "kexidynamicqueryparameterdialog.h" +#include "kexidynamicqueryparameterdialog.moc" + +#include <qvbox.h> +#include <klocale.h> +#include <kdebug.h> +#include <qlineedit.h> +#include <qobjectlist.h> + +KexiDynamicQueryParameterDialog::KexiDynamicQueryParameterDialog(QWidget *parent, + KexiDataProvider::Parameters *values, const KexiDataProvider::ParameterList &list): + KDialogBase(parent, "paramddialog", true, i18n("Query Parameters"), + KDialogBase::Ok|KDialogBase::Cancel, KDialogBase::Ok, true ) +{ + m_values=values; + int y; + m_mainView=new QVBox(this); + + for (KexiDataProvider::ParameterList::const_iterator it=list.begin(); + it!=list.end();++it) { + QLineEdit *le=new QLineEdit(m_mainView,(*it).name.utf8()); + le->setText((*values)[(*it).name]); + } + + setMainWidget(m_mainView); +} + +KexiDynamicQueryParameterDialog::~KexiDynamicQueryParameterDialog() {} + +void KexiDynamicQueryParameterDialog::slotOk() { + QObjectList *l=queryList(0,"kexi_.*",true,true); + QObjectListIt it(*l); + QObject *obj; + kdDebug()<<"KexiDynamicQueryParameterDialog::slotOk()"<<endl; + while ((obj=it.current())!=0) { + kdDebug()<<"KexiDynamicQueryParameterDialog::slotOk()::loop"<<endl; + (*m_values)[QString().fromUtf8(obj->name())]= + (dynamic_cast<QLineEdit*>(obj))->text(); + ++it; + } + delete l; + KDialogBase::slotOk(); +} diff --git a/kexi/plugins/queries/kexidynamicqueryparameterdialog.h b/kexi/plugins/queries/kexidynamicqueryparameterdialog.h new file mode 100644 index 00000000..b315e4f9 --- /dev/null +++ b/kexi/plugins/queries/kexidynamicqueryparameterdialog.h @@ -0,0 +1,45 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Joseph Wenninger <jowenn@kde.org> + + 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. + */ + +#ifndef _KEXI_DYNAMIC_QUERY_PARAMETER_DIALOG_H_ +#define _KEXI_DYNAMIC_QUERY_PARAMETER_DIALOG_H_ + + +#include <kdialogbase.h> +#include <kexidataprovider.h> + +class QVBox; + +class KexiDynamicQueryParameterDialog : public KDialogBase +{ + Q_OBJECT +public: + KexiDynamicQueryParameterDialog(QWidget *parent,KexiDataProvider::Parameters *, const KexiDataProvider::ParameterList &); + virtual ~KexiDynamicQueryParameterDialog(); + +protected: + virtual void slotOk(); +private: +//temporary only. Later a different widget will be used + QVBox *m_mainView; + KexiDataProvider::Parameters *m_values; + +}; + +#endif diff --git a/kexi/plugins/queries/kexiparameterlisteditor.ui b/kexi/plugins/queries/kexiparameterlisteditor.ui new file mode 100644 index 00000000..ac4a3230 --- /dev/null +++ b/kexi/plugins/queries/kexiparameterlisteditor.ui @@ -0,0 +1,88 @@ +<!DOCTYPE UI><UI version="3.1" stdsetdef="1"> +<class>KexiParameterListEditor</class> +<widget class="QWidget"> + <property name="name"> + <cstring>KexiParameterListEditor</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>190</width> + <height>480</height> + </rect> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>textLabel1</cstring> + </property> + <property name="text"> + <string>Parameters:</string> + </property> + </widget> + <widget class="KListView"> + <column> + <property name="text"> + <string>Name</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <column> + <property name="text"> + <string>Type</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <property name="name"> + <cstring>list</cstring> + </property> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout1</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="KPushButton"> + <property name="name"> + <cstring>addParameter</cstring> + </property> + <property name="text"> + <string>Add</string> + </property> + </widget> + <widget class="KPushButton"> + <property name="name"> + <cstring>deleteParameter</cstring> + </property> + <property name="text"> + <string>Delete</string> + </property> + </widget> + </hbox> + </widget> + </vbox> +</widget> +<layoutdefaults spacing="6" margin="11"/> +<includehints> + <includehint>klistview.h</includehint> + <includehint>kpushbutton.h</includehint> + <includehint>kpushbutton.h</includehint> +</includehints> +</UI> diff --git a/kexi/plugins/queries/kexiquerydesignerguieditor.cpp b/kexi/plugins/queries/kexiquerydesignerguieditor.cpp new file mode 100644 index 00000000..d67573e8 --- /dev/null +++ b/kexi/plugins/queries/kexiquerydesignerguieditor.cpp @@ -0,0 +1,1803 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Lucijan Busch <lucijan@kde.org> + Copyright (C) 2004-2006 Jaroslaw Staniek <js@iidea.pl> + + This library 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 library 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 library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "kexiquerydesignerguieditor.h" + +#include <qlayout.h> +#include <qpainter.h> +#include <qdom.h> +#include <qregexp.h> + +#include <kdebug.h> +#include <klocale.h> +#include <kmessagebox.h> + +#include <kexidb/field.h> +#include <kexidb/queryschema.h> +#include <kexidb/connection.h> +#include <kexidb/parser/parser.h> +#include <kexidb/parser/sqlparser.h> +#include <kexidb/utils.h> +#include <kexidb/roweditbuffer.h> +#include <kexiutils/identifier.h> +#include <kexiproject.h> +#include <keximainwindow.h> +#include <kexiinternalpart.h> +#include <kexitableview.h> +#include <kexitableitem.h> +#include <kexitableviewdata.h> +#include <kexidragobjects.h> +#include <kexidialogbase.h> +#include <kexidatatable.h> +#include <kexi.h> +#include <kexisectionheader.h> +#include <widget/tableview/kexidataawarepropertyset.h> +#include <widget/relations/kexirelationwidget.h> +#include <widget/relations/kexirelationviewtable.h> +#include <koproperty/property.h> +#include <koproperty/set.h> + +#include "kexiquerypart.h" + +//! @todo remove KEXI_NO_QUERY_TOTALS later +#define KEXI_NO_QUERY_TOTALS + +//! indices for table columns +#define COLUMN_ID_COLUMN 0 +#define COLUMN_ID_TABLE 1 +#define COLUMN_ID_VISIBLE 2 +#ifdef KEXI_NO_QUERY_TOTALS +# define COLUMN_ID_SORTING 3 +# define COLUMN_ID_CRITERIA 4 +#else +# define COLUMN_ID_TOTALS 3 +# define COLUMN_ID_SORTING 4 +# define COLUMN_ID_CRITERIA 5 +#endif + +/*! @internal */ +class KexiQueryDesignerGuiEditor::Private +{ +public: + Private() + : fieldColumnIdentifiers(101, false/*case insens.*/) + { + droppedNewItem = 0; + slotTableAdded_enabled = true; + } + + bool changeSingleCellValue(KexiTableItem &item, int columnNumber, + const QVariant& value, KexiDB::ResultInfo* result) + { + data->clearRowEditBuffer(); + if (!data->updateRowEditBuffer(&item, columnNumber, value) + || !data->saveRowChanges(item, true)) + { + if (result) + *result = *data->result(); + return false; + } + return true; + } + + KexiTableViewData *data; + KexiDataTable *dataTable; + QGuardedPtr<KexiDB::Connection> conn; + + KexiRelationWidget *relations; + KexiSectionHeader *head; + QSplitter *spl; + + /*! Used to remember in slotDroppedAtRow() what data was dropped, + so we can create appropriate prop. set in slotRowInserted() + This information is cached and entirely refreshed on updateColumnsData(). */ + KexiTableViewData *fieldColumnData, *tablesColumnData; + + /*! Collects identifiers selected in 1st (field) column, + so we're able to distinguish between table identifiers selected from + the dropdown list, and strings (e.g. expressions) entered by hand. + This information is cached and entirely refreshed on updateColumnsData(). + The dict is filled with (char*)1 values (doesn't matter what it is); + */ + QDict<char> fieldColumnIdentifiers; + + KexiDataAwarePropertySet* sets; + KexiTableItem *droppedNewItem; + + QString droppedNewTable, droppedNewField; + + bool slotTableAdded_enabled : 1; +}; + +static bool isAsterisk(const QString& tableName, const QString& fieldName) +{ + return tableName=="*" || fieldName.endsWith("*"); +} + +//! @internal \return true if sorting is allowed for \a fieldName and \a tableName +static bool sortingAllowed(const QString& fieldName, const QString& tableName) { + return ! (fieldName=="*" || (fieldName.isEmpty() && tableName=="*")); +} + +//========================================================= + +KexiQueryDesignerGuiEditor::KexiQueryDesignerGuiEditor( + KexiMainWindow *mainWin, QWidget *parent, const char *name) + : KexiViewBase(mainWin, parent, name) + , d( new Private() ) +{ + d->conn = mainWin->project()->dbConnection(); + + d->spl = new QSplitter(Vertical, this); + d->spl->setChildrenCollapsible(false); + d->relations = new KexiRelationWidget(mainWin, d->spl, "relations"); + connect(d->relations, SIGNAL(tableAdded(KexiDB::TableSchema&)), + this, SLOT(slotTableAdded(KexiDB::TableSchema&))); + connect(d->relations, SIGNAL(tableHidden(KexiDB::TableSchema&)), + this, SLOT(slotTableHidden(KexiDB::TableSchema&))); + connect(d->relations, SIGNAL(tableFieldDoubleClicked(KexiDB::TableSchema*,const QString&)), + this, SLOT(slotTableFieldDoubleClicked(KexiDB::TableSchema*,const QString&))); + + d->head = new KexiSectionHeader(i18n("Query Columns"), Vertical, d->spl); + d->dataTable = new KexiDataTable(mainWin, d->head, "guieditor_dataTable", false); + d->dataTable->dataAwareObject()->setSpreadSheetMode(); + + d->data = new KexiTableViewData(); //just empty data + d->sets = new KexiDataAwarePropertySet( this, d->dataTable->dataAwareObject() ); + initTableColumns(); + initTableRows(); + + QValueList<int> c; + c << COLUMN_ID_COLUMN << COLUMN_ID_TABLE << COLUMN_ID_CRITERIA; + if (d->dataTable->tableView()/*sanity*/) { + d->dataTable->tableView()->adjustColumnWidthToContents(COLUMN_ID_VISIBLE); + d->dataTable->tableView()->adjustColumnWidthToContents(COLUMN_ID_SORTING); + d->dataTable->tableView()->maximizeColumnsWidth( c ); + d->dataTable->tableView()->setDropsAtRowEnabled(true); + connect(d->dataTable->tableView(), SIGNAL(dragOverRow(KexiTableItem*,int,QDragMoveEvent*)), + this, SLOT(slotDragOverTableRow(KexiTableItem*,int,QDragMoveEvent*))); + connect(d->dataTable->tableView(), SIGNAL(droppedAtRow(KexiTableItem*,int,QDropEvent*,KexiTableItem*&)), + this, SLOT(slotDroppedAtRow(KexiTableItem*,int,QDropEvent*,KexiTableItem*&))); + connect(d->dataTable->tableView(), SIGNAL(newItemAppendedForAfterDeletingInSpreadSheetMode()), + this, SLOT(slotNewItemAppendedForAfterDeletingInSpreadSheetMode())); + } + connect(d->data, SIGNAL(aboutToChangeCell(KexiTableItem*,int,QVariant&,KexiDB::ResultInfo*)), + this, SLOT(slotBeforeCellChanged(KexiTableItem*,int,QVariant&,KexiDB::ResultInfo*))); + connect(d->data, SIGNAL(rowInserted(KexiTableItem*,uint,bool)), + this, SLOT(slotRowInserted(KexiTableItem*,uint,bool))); + connect(d->relations, SIGNAL(tablePositionChanged(KexiRelationViewTableContainer*)), + this, SLOT(slotTablePositionChanged(KexiRelationViewTableContainer*))); + connect(d->relations, SIGNAL(aboutConnectionRemove(KexiRelationViewConnection*)), + this, SLOT(slotAboutConnectionRemove(KexiRelationViewConnection*))); + + QVBoxLayout *l = new QVBoxLayout(this); + l->addWidget(d->spl); + + addChildView(d->relations); + addChildView(d->dataTable); + setViewWidget(d->dataTable, true); + d->relations->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding); + d->head->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding); + updateGeometry(); + d->spl->setSizes(QValueList<int>()<< 800<<400); +} + +KexiQueryDesignerGuiEditor::~KexiQueryDesignerGuiEditor() +{ +} + +void +KexiQueryDesignerGuiEditor::initTableColumns() +{ + KexiTableViewColumn *col1 = new KexiTableViewColumn("column", KexiDB::Field::Enum, i18n("Column"), + i18n("Describes field name or expression for the designed query.")); + col1->setRelatedDataEditable(true); + + d->fieldColumnData = new KexiTableViewData(KexiDB::Field::Text, KexiDB::Field::Text); + col1->setRelatedData( d->fieldColumnData ); + d->data->addColumn(col1); + + KexiTableViewColumn *col2 = new KexiTableViewColumn("table", KexiDB::Field::Enum, i18n("Table"), + i18n("Describes table for a given field. Can be empty.")); + d->tablesColumnData = new KexiTableViewData(KexiDB::Field::Text, KexiDB::Field::Text); + col2->setRelatedData( d->tablesColumnData ); + d->data->addColumn(col2); + + KexiTableViewColumn *col3 = new KexiTableViewColumn("visible", KexiDB::Field::Boolean, i18n("Visible"), + i18n("Describes visibility for a given field or expression.")); + col3->field()->setDefaultValue( QVariant(false, 0) ); + col3->field()->setNotNull( true ); + d->data->addColumn(col3); + +#ifndef KEXI_NO_QUERY_TOTALS + KexiTableViewColumn *col4 = new KexiTableViewColumn("totals", KexiDB::Field::Enum, i18n("Totals"), + i18n("Describes a way of computing totals for a given field or expression.")); + QValueVector<QString> totalsTypes; + totalsTypes.append( i18n("Group by") ); + totalsTypes.append( i18n("Sum") ); + totalsTypes.append( i18n("Average") ); + totalsTypes.append( i18n("Min") ); + totalsTypes.append( i18n("Max") ); + //todo: more like this + col4->field()->setEnumHints(totalsTypes); + d->data->addColumn(col4); +#endif + + KexiTableViewColumn *col5 = new KexiTableViewColumn("sort", KexiDB::Field::Enum, i18n("Sorting"), + i18n("Describes a way of sorting for a given field.")); + QValueVector<QString> sortTypes; + sortTypes.append( "" ); + sortTypes.append( i18n("Ascending") ); + sortTypes.append( i18n("Descending") ); + col5->field()->setEnumHints(sortTypes); + d->data->addColumn(col5); + + KexiTableViewColumn *col6 = new KexiTableViewColumn("criteria", KexiDB::Field::Text, i18n("Criteria"), + i18n("Describes the criteria for a given field or expression.")); + d->data->addColumn(col6); + +// KexiTableViewColumn *col7 = new KexiTableViewColumn(i18n("Or"), KexiDB::Field::Text); +// d->data->addColumn(col7); +} + +void KexiQueryDesignerGuiEditor::initTableRows() +{ + d->data->deleteAllRows(); + //const int columns = d->data->columnsCount(); + for (int i=0; i<(int)d->sets->size(); i++) { + KexiTableItem* item; + d->data->append(item = d->data->createItem()); + item->at(COLUMN_ID_VISIBLE) = QVariant(false, 0); + } + d->dataTable->dataAwareObject()->setData(d->data); + + updateColumnsData(); +} + +void KexiQueryDesignerGuiEditor::updateColumnsData() +{ + d->dataTable->dataAwareObject()->acceptRowEdit(); + + QStringList sortedTableNames; + for (TablesDictIterator it(*d->relations->tables());it.current();++it) + sortedTableNames += it.current()->schema()->name(); + qHeapSort( sortedTableNames ); + + //several tables can be hidden now, so remove rows for these tables + QValueList<int> rowsToDelete; + for (int r = 0; r<(int)d->sets->size(); r++) { + KoProperty::Set *set = d->sets->at(r); + if (set) { + QString tableName = (*set)["table"].value().toString(); + QString fieldName = (*set)["field"].value().toString(); + const bool allTablesAsterisk = tableName=="*" && d->relations->tables()->isEmpty(); + const bool fieldNotFound = tableName!="*" + && !(*set)["isExpression"].value().toBool() + && sortedTableNames.end() == qFind( sortedTableNames.begin(), sortedTableNames.end(), tableName ); + + if (allTablesAsterisk || fieldNotFound) { + //table not found: mark this line for later removal + rowsToDelete += r; + } + } + } + d->data->deleteRows( rowsToDelete ); + + //update 'table' and 'field' columns + d->tablesColumnData->deleteAllRows(); + d->fieldColumnData->deleteAllRows(); + d->fieldColumnIdentifiers.clear(); + + KexiTableItem *item = d->fieldColumnData->createItem(); //new KexiTableItem(2); + (*item)[COLUMN_ID_COLUMN]="*"; + (*item)[COLUMN_ID_TABLE]="*"; + d->fieldColumnData->append( item ); + d->fieldColumnIdentifiers.insert((*item)[COLUMN_ID_COLUMN].toString(), (char*)1); //cache + +// tempData()->clearQuery(); + tempData()->unregisterForTablesSchemaChanges(); + for (QStringList::const_iterator it = sortedTableNames.constBegin(); + it!=sortedTableNames.constEnd(); ++it) + { + //table +/*! @todo what about query? */ + KexiDB::TableSchema *table = d->relations->tables()->find(*it)->schema()->table(); + d->conn->registerForTableSchemaChanges(*tempData(), *table); //this table will be used + item = d->tablesColumnData->createItem(); //new KexiTableItem(2); + (*item)[COLUMN_ID_COLUMN]=table->name(); + (*item)[COLUMN_ID_TABLE]=(*item)[COLUMN_ID_COLUMN]; + d->tablesColumnData->append( item ); + //fields + item = d->fieldColumnData->createItem(); //new KexiTableItem(2); + (*item)[COLUMN_ID_COLUMN]=table->name()+".*"; + (*item)[COLUMN_ID_TABLE]=(*item)[COLUMN_ID_COLUMN]; + d->fieldColumnData->append( item ); + d->fieldColumnIdentifiers.insert((*item)[COLUMN_ID_COLUMN].toString(), (char*)1); //cache + for (KexiDB::Field::ListIterator t_it = table->fieldsIterator();t_it.current();++t_it) { + item = d->fieldColumnData->createItem(); // new KexiTableItem(2); + (*item)[COLUMN_ID_COLUMN]=table->name()+"."+t_it.current()->name(); + (*item)[COLUMN_ID_TABLE]=QString(" ") + t_it.current()->name(); + d->fieldColumnData->append( item ); + d->fieldColumnIdentifiers.insert((*item)[COLUMN_ID_COLUMN].toString(), (char*)1); //cache + } + } +//TODO +} + +KexiRelationWidget *KexiQueryDesignerGuiEditor::relationView() const +{ + return d->relations; +} + +KexiQueryPart::TempData * +KexiQueryDesignerGuiEditor::tempData() const +{ + return static_cast<KexiQueryPart::TempData*>(parentDialog()->tempData()); +} + +static QString msgCannotSwitch_EmptyDesign() { + return i18n("Cannot switch to data view, because query design is empty.\n" + "First, please create your design."); +} + +bool +KexiQueryDesignerGuiEditor::buildSchema(QString *errMsg) +{ + //build query schema + KexiQueryPart::TempData * temp = tempData(); + if (temp->query()) { + temp->clearQuery(); + } else { + temp->setQuery( new KexiDB::QuerySchema() ); + } + + //add tables + for (TablesDictIterator it(*d->relations->tables()); it.current(); ++it) { +/*! @todo what about query? */ + temp->query()->addTable( it.current()->schema()->table() ); + } + + //add fields, also build: + // -WHERE expression + // -ORDER BY list + KexiDB::BaseExpr *whereExpr = 0; + const uint count = QMIN(d->data->count(), d->sets->size()); + bool fieldsFound = false; + KexiTableViewData::Iterator it(d->data->iterator()); + for (uint i=0; i<count && it.current(); ++it, i++) { + if (!it.current()->at(COLUMN_ID_TABLE).isNull() && it.current()->at(COLUMN_ID_COLUMN).isNull()) { + //show message about missing field name, and set focus to that cell + kexipluginsdbg << "no field provided!" << endl; + d->dataTable->dataAwareObject()->setCursorPosition(i,0); + if (errMsg) + *errMsg = i18n("Select column for table \"%1\"") + .arg(it.current()->at(COLUMN_ID_TABLE).toString()); + return false; + } + + KoProperty::Set *set = d->sets->at(i); + if (set) { + QString tableName = (*set)["table"].value().toString().stripWhiteSpace(); + QString fieldName = (*set)["field"].value().toString(); + QString fieldAndTableName = fieldName; + KexiDB::Field *currentField = 0; // will be set if this column is a single field + KexiDB::QueryColumnInfo* currentColumn = 0; + if (!tableName.isEmpty()) + fieldAndTableName.prepend(tableName+"."); + const bool fieldVisible = (*set)["visible"].value().toBool(); + QString criteriaStr = (*set)["criteria"].value().toString(); + QCString alias = (*set)["alias"].value().toCString(); + if (!criteriaStr.isEmpty()) { + int token; + KexiDB::BaseExpr *criteriaExpr = parseExpressionString(criteriaStr, token, + true/*allowRelationalOperator*/); + if (!criteriaExpr) {//for sanity + if (errMsg) + *errMsg = i18n("Invalid criteria \"%1\"").arg(criteriaStr); + delete whereExpr; + return false; + } + //build relational expression for column variable + KexiDB::VariableExpr *varExpr = new KexiDB::VariableExpr(fieldAndTableName); + criteriaExpr = new KexiDB::BinaryExpr(KexiDBExpr_Relational, varExpr, token, criteriaExpr); + //critera ok: add it to WHERE section + if (whereExpr) + whereExpr = new KexiDB::BinaryExpr(KexiDBExpr_Logical, whereExpr, AND, criteriaExpr); + else //first expr. + whereExpr = criteriaExpr; + } + if (tableName.isEmpty()) { + if ((*set)["isExpression"].value().toBool()==true) { + //add expression column + int dummyToken; + KexiDB::BaseExpr *columnExpr = parseExpressionString(fieldName, dummyToken, + false/*!allowRelationalOperator*/); + if (!columnExpr) { + if (errMsg) + *errMsg = i18n("Invalid expression \"%1\"").arg(fieldName); + return false; + } + temp->query()->addExpression(columnExpr, fieldVisible); + if (fieldVisible) + fieldsFound = true; + if (!alias.isEmpty()) + temp->query()->setColumnAlias( temp->query()->fieldCount()-1, alias ); + } + //TODO + } + else if (tableName=="*") { + //all tables asterisk + temp->query()->addAsterisk( new KexiDB::QueryAsterisk( temp->query(), 0 ), fieldVisible ); + if (fieldVisible) + fieldsFound = true; + continue; + } + else { + KexiDB::TableSchema *t = d->conn->tableSchema(tableName); + if (fieldName=="*") { + //single-table asterisk: <tablename> + ".*" + number + temp->query()->addAsterisk( new KexiDB::QueryAsterisk( temp->query(), t ), fieldVisible ); + if (fieldVisible) + fieldsFound = true; + } else { + if (!t) { + kexipluginswarn << "query designer: NO TABLE '" + << (*set)["table"].value().toString() << "'" << endl; + continue; + } + currentField = t->field( fieldName ); + if (!currentField) { + kexipluginswarn << "query designer: NO FIELD '" << fieldName << "'" << endl; + continue; + } + if (!fieldVisible && criteriaStr.isEmpty() && (*set)["isExpression"] + && (*set)["sorting"].value().toString()!="nosorting") + { + kexipluginsdbg << "invisible field with sorting: do not add it to the fields list" << endl; + continue; + } + temp->query()->addField(currentField, fieldVisible); + currentColumn = temp->query()->expandedOrInternalField( + temp->query()->fieldsExpanded().count() - 1 ); + if (fieldVisible) + fieldsFound = true; + if (!alias.isEmpty()) + temp->query()->setColumnAlias( temp->query()->fieldCount()-1, alias ); + } + } + } + else {//!set + kexipluginsdbg << it.current()->at(COLUMN_ID_TABLE).toString() << endl; + } + } + if (!fieldsFound) { + if (errMsg) + *errMsg = msgCannotSwitch_EmptyDesign(); + return false; + } + if (whereExpr) + kexipluginsdbg << "KexiQueryDesignerGuiEditor::buildSchema(): setting CRITERIA: " + << whereExpr->debugString() << endl; + + //set always, because if whereExpr==NULL, + //this will clear prev. expr + temp->query()->setWhereExpression( whereExpr ); + + //add relations (looking for connections) + for (ConnectionListIterator it(*d->relations->connections()); it.current(); ++it) { + KexiRelationViewTableContainer *masterTable = it.current()->masterTable(); + KexiRelationViewTableContainer *detailsTable = it.current()->detailsTable(); + +/*! @todo what about query? */ + temp->query()->addRelationship( + masterTable->schema()->table()->field(it.current()->masterField()), + detailsTable->schema()->table()->field(it.current()->detailsField()) ); + } + + // Add sorting information (ORDER BY) - we can do that only now + // after all QueryColumnInfo items are instantiated + KexiDB::OrderByColumnList orderByColumns; + it = d->data->iterator(); + int fieldNumber = -1; //field number (empty rows are omitted) + for (uint i=0/*row number*/; i<count && it.current(); ++it, i++) { + KoProperty::Set *set = d->sets->at(i); + if (!set) + continue; + fieldNumber++; + KexiDB::Field *currentField = 0; + KexiDB::QueryColumnInfo *currentColumn = 0; + QString sortingString( (*set)["sorting"].value().toString() ); + if (sortingString!="ascending" && sortingString!="descending") + continue; + if (!(*set)["visible"].value().toBool()) { + // this row defines invisible field but contains sorting information, + // what means KexiDB::Field should be used as a reference for this sorting + // Note1: alias is not supported here. + + // Try to find a field (not mentioned after SELECT): + currentField = temp->query()->findTableField( (*set)["field"].value().toString() ); + if (!currentField) { + kexipluginswarn << "KexiQueryDesignerGuiEditor::buildSchema(): NO FIELD '" + << (*set)["field"].value().toString() + << " available for sorting" << endl; + continue; + } + orderByColumns.appendField(*currentField, sortingString=="ascending"); + continue; + } + currentField = temp->query()->field( (uint)fieldNumber ); + if (!currentField || currentField->isExpression() || currentField->isQueryAsterisk()) +//! @todo support expressions here + continue; +//! @todo ok, but not for expressions + QString aliasString( (*set)["alias"].value().toString() ); + currentColumn = temp->query()->columnInfo( + (*set)["table"].value().toString() + "." + + (aliasString.isEmpty() ? currentField->name() : aliasString) ); + if (currentField && currentColumn) { + if (currentColumn->visible) + orderByColumns.appendColumn(*currentColumn, sortingString=="ascending"); + else if (currentColumn->field) + orderByColumns.appendField(*currentColumn->field, sortingString=="ascending"); + } + } + temp->query()->setOrderByColumnList( orderByColumns ); + + temp->query()->debug(); + temp->registerTableSchemaChanges(temp->query()); + //TODO? + return true; +} + +tristate +KexiQueryDesignerGuiEditor::beforeSwitchTo(int mode, bool &dontStore) +{ + kexipluginsdbg << "KexiQueryDesignerGuiEditor::beforeSwitch()" << mode << endl; + + if (!d->dataTable->dataAwareObject()->acceptRowEdit()) + return cancelled; + + if (mode==Kexi::DesignViewMode) { + return true; + } + else if (mode==Kexi::DataViewMode) { +// if (!d->dataTable->dataAwareObject()->acceptRowEdit()) + // return cancelled; + + if (!dirty() && parentDialog()->neverSaved()) { + KMessageBox::information(this, msgCannotSwitch_EmptyDesign()); + return cancelled; + } + if (dirty() || !tempData()->query()) { + //remember current design in a temporary structure + dontStore=true; + QString errMsg; + //build schema; problems are not allowed + if (!buildSchema(&errMsg)) { + KMessageBox::sorry(this, errMsg); + return cancelled; + } + } + //TODO + return true; + } + else if (mode==Kexi::TextViewMode) { + dontStore=true; + //build schema; ignore problems + buildSchema(); +/* if (tempData()->query && tempData()->query->fieldCount()==0) { + //no fields selected: let's add "*" (all-tables asterisk), + // otherwise SQL statement will be invalid + tempData()->query->addAsterisk( new KexiDB::QueryAsterisk( tempData()->query ) ); + }*/ + //todo + return true; + } + + return false; +} + +tristate +KexiQueryDesignerGuiEditor::afterSwitchFrom(int mode) +{ + const bool was_dirty = dirty(); + KexiDB::Connection *conn = parentDialog()->mainWin()->project()->dbConnection(); + if (mode==Kexi::NoViewMode || (mode==Kexi::DataViewMode && !tempData()->query())) { + //this is not a SWITCH but a fresh opening in this view mode + if (!m_dialog->neverSaved()) { + if (!loadLayout()) { + //err msg + parentDialog()->setStatus(conn, + i18n("Query definition loading failed."), + i18n("Query design may be corrupted so it could not be opened even in text view.\n" + "You can delete the query and create it again.")); + return false; + } + // Invalid queries case: + // KexiDialogBase::switchToViewMode() first opens DesignViewMode, + // and then KexiQueryPart::loadSchemaData() doesn't allocate QuerySchema object + // do we're carefully looking at parentDialog()->schemaData() + KexiDB::QuerySchema * q = dynamic_cast<KexiDB::QuerySchema *>(parentDialog()->schemaData()); + if (q) { + KexiDB::ResultInfo result; + showFieldsForQuery( q, result ); + if (!result.success) { + parentDialog()->setStatus(&result, i18n("Query definition loading failed.")); + tempData()->proposeOpeningInTextViewModeBecauseOfProblems = true; + return false; + } + } +//! @todo load global query properties + } + } + else if (mode==Kexi::TextViewMode || mode==Kexi::DataViewMode) { + // Switch from text or data view. In the second case, the design could be changed as well + // because there could be changes made in the text view before switching to the data view. + if (tempData()->queryChangedInPreviousView) { + //previous view changed query data + //-clear and regenerate GUI items + initTableRows(); + //todo + if (tempData()->query()) { + //there is a query schema to show + showTablesForQuery( tempData()->query() ); + //-show fields + KexiDB::ResultInfo result; + showFieldsAndRelationsForQuery( tempData()->query(), result ); + if (!result.success) { + parentDialog()->setStatus(&result, i18n("Query definition loading failed.")); + return false; + } + } + else { + d->relations->clear(); + } + } +//! @todo load global query properties + } + + if (mode==Kexi::DataViewMode) { + //this is just a SWITCH from data view + //set cursor if needed: + if (d->dataTable->dataAwareObject()->currentRow()<0 + || d->dataTable->dataAwareObject()->currentColumn()<0) + { + d->dataTable->dataAwareObject()->ensureCellVisible(0,0); + d->dataTable->dataAwareObject()->setCursorPosition(0,0); + } + } + tempData()->queryChangedInPreviousView = false; + setFocus(); //to allow shared actions proper update + if (!was_dirty) + setDirty(false); + return true; +} + + +KexiDB::SchemaData* +KexiQueryDesignerGuiEditor::storeNewData(const KexiDB::SchemaData& sdata, bool &cancel) +{ + if (!d->dataTable->dataAwareObject()->acceptRowEdit()) { + cancel = true; + return 0; + } + QString errMsg; + KexiQueryPart::TempData * temp = tempData(); + if (!temp->query() || !(viewMode()==Kexi::DesignViewMode && !temp->queryChangedInPreviousView)) { + //only rebuild schema if it has not been rebuilt previously + if (!buildSchema(&errMsg)) { + KMessageBox::sorry(this, errMsg); + cancel = true; + return 0; + } + } + (KexiDB::SchemaData&)*temp->query() = sdata; //copy main attributes + + bool ok = m_mainWin->project()->dbConnection()->storeObjectSchemaData( *temp->query(), true /*newObject*/ ); + m_dialog->setId( temp->query()->id() ); + + if (ok) + ok = storeLayout(); + +// temp->query = 0; //will be returned, so: don't keep it + if (!ok) { + temp->setQuery( 0 ); +// delete query; + return 0; + } + return temp->takeQuery(); //will be returned, so: don't keep it in temp +} + +tristate KexiQueryDesignerGuiEditor::storeData(bool dontAsk) +{ + if (!d->dataTable->dataAwareObject()->acceptRowEdit()) + return cancelled; + + const bool was_dirty = dirty(); + tristate res = KexiViewBase::storeData(dontAsk); //this clears dirty flag + if (true == res) + res = buildSchema(); + if (true == res) + res = storeLayout(); + if (true != res) { + if (was_dirty) + setDirty(true); + } + return res; +} + +void KexiQueryDesignerGuiEditor::showTablesForQuery(KexiDB::QuerySchema *query) +{ +//replaced by code below that preserves geometries d->relations->clear(); + + // instead of hiding all tables and showing some tables, + // show only these new and hide these unncecessary; the same for connections) + d->slotTableAdded_enabled = false; //speedup + d->relations->removeAllConnections(); //connections will be recreated + d->relations->hideAllTablesExcept( query->tables() ); + for (KexiDB::TableSchema::ListIterator it(*query->tables()); it.current(); ++it) { + d->relations->addTable( it.current() ); + } + + d->slotTableAdded_enabled = true; + updateColumnsData(); +} + +void KexiQueryDesignerGuiEditor::addConnection( + KexiDB::Field *masterField, KexiDB::Field *detailsField) +{ + SourceConnection conn; + conn.masterTable = masterField->table()->name(); //<<<TODO + conn.masterField = masterField->name(); + conn.detailsTable = detailsField->table()->name(); + conn.detailsField = detailsField->name(); + d->relations->addConnection( conn ); +} + +void KexiQueryDesignerGuiEditor::showFieldsForQuery(KexiDB::QuerySchema *query, KexiDB::ResultInfo& result) +{ + showFieldsOrRelationsForQueryInternal(query, true, false, result); +} + +void KexiQueryDesignerGuiEditor::showRelationsForQuery(KexiDB::QuerySchema *query, KexiDB::ResultInfo& result) +{ + showFieldsOrRelationsForQueryInternal(query, false, true, result); +} + +void KexiQueryDesignerGuiEditor::showFieldsAndRelationsForQuery(KexiDB::QuerySchema *query, + KexiDB::ResultInfo& result) +{ + showFieldsOrRelationsForQueryInternal(query, true, true, result); +} + +void KexiQueryDesignerGuiEditor::showFieldsOrRelationsForQueryInternal( + KexiDB::QuerySchema *query, bool showFields, bool showRelations, KexiDB::ResultInfo& result) +{ + result.clear(); + const bool was_dirty = dirty(); + + //1. Show explicitly declared relations: + if (showRelations) { + KexiDB::Relationship *rel; + for (KexiDB::Relationship::ListIterator it(*query->relationships()); + (rel=it.current()); ++it) + { +//! @todo: now only sigle-field relationships are implemented! + KexiDB::Field *masterField = rel->masterIndex()->fields()->first(); + KexiDB::Field *detailsField = rel->detailsIndex()->fields()->first(); + addConnection(masterField, detailsField); + } + } + + //2. Collect information about criterias + // --this must be top level chain of AND's + // --this will also show joins as: [table1.]field1 = [table2.]field2 + QDict<KexiDB::BaseExpr> criterias(101, false); + KexiDB::BaseExpr* e = query->whereExpression(); + KexiDB::BaseExpr* eItem = 0; + while (e) { + //eat parentheses because the expression can be (....) AND (... AND ... ) + while (e && e->toUnary() && e->token()=='(') + e = e->toUnary()->arg(); + + if (e->toBinary() && e->token()==AND) { + eItem = e->toBinary()->left(); + e = e->toBinary()->right(); + } + else { + eItem = e; + e = 0; + } + + //eat parentheses + while (eItem && eItem->toUnary() && eItem->token()=='(') + eItem = eItem->toUnary()->arg(); + + if (!eItem) + continue; + + kexidbg << eItem->toString() << endl; + KexiDB::BinaryExpr* binary = eItem->toBinary(); + if (binary && eItem->exprClass()==KexiDBExpr_Relational) { + KexiDB::Field *leftField = 0, *rightField = 0; + if (eItem->token()=='=' + && binary->left()->toVariable() + && binary->right()->toVariable() + && (leftField = query->findTableField( binary->left()->toString() )) + && (rightField = query->findTableField( binary->right()->toString() ))) + { +//! @todo move this check to parser on QuerySchema creation +//! or to QuerySchema creation (WHERE expression should be then simplified +//! by removing joins + + //this is relationship defined as following JOIN: [table1.]field1 = [table2.]field2 + if (showRelations) { +//! @todo testing primary key here is too simplified; maybe look ar isForeignKey() or indices.. +//! @todo what about multifield joins? + if (leftField->isPrimaryKey()) + addConnection(leftField /*master*/, rightField /*details*/); + else + addConnection(rightField /*master*/, leftField /*details*/); +//! @todo addConnection() should have "bool oneToOne" arg, for 1-to-1 relations + } + } + else if (binary->left()->toVariable()) { + //this is: variable , op , argument + //store variable -> argument: + criterias.insert(binary->left()->toVariable()->name, binary->right()); + } + else if (binary->right()->toVariable()) { + //this is: argument , op , variable + //store variable -> argument: + criterias.insert(binary->right()->toVariable()->name, binary->left()); + } + } + } //while + + if (!showFields) + return; + + //3. show fields (including * and table.*) + uint row_num = 0; + KexiDB::Field *field; + QPtrDict<char> usedCriterias(101); // <-- used criterias will be saved here + // so in step 4. we will be able to add + // remaining invisible columns with criterias + for (KexiDB::Field::ListIterator it(*query->fields()); + (field = it.current()); ++it, row_num++) + { + //append a new row + QString tableName, fieldName, columnAlias, criteriaString; + KexiDB::BinaryExpr *criteriaExpr = 0; + KexiDB::BaseExpr *criteriaArgument = 0; + if (field->isQueryAsterisk()) { + if (field->table()) {//single-table asterisk + tableName = field->table()->name(); + fieldName = "*"; + } + else {//all-tables asterisk + tableName = "*"; + fieldName = ""; + } + } + else { + columnAlias = query->columnAlias(row_num); + if (field->isExpression()) { +// if (columnAlias.isEmpty()) { +// columnAlias = i18n("expression", "expr%1").arg(row_num); //TODO +// } +// if (columnAlias.isEmpty()) +//TODO: ok? perhaps do not allow to omit aliases? + fieldName = field->expression()->toString(); +// else +// fieldName = columnAlias + ": " + field->expression()->toString(); + } + else { + tableName = field->table()->name(); + fieldName = field->name(); + criteriaArgument = criterias[fieldName]; + if (!criteriaArgument) {//try table.field + criteriaArgument = criterias[tableName+"."+fieldName]; + } + if (criteriaArgument) {//criteria expression is just a parent of argument + criteriaExpr = criteriaArgument->parent()->toBinary(); + usedCriterias.insert(criteriaArgument, (char*)1); //save info. about used criteria + } + } + } + //create new row data + KexiTableItem *newItem = createNewRow(tableName, fieldName, true /* visible*/); + if (criteriaExpr) { +//! @todo fix for !INFIX operators + if (criteriaExpr->token()=='=') + criteriaString = criteriaArgument->toString(); + else + criteriaString = criteriaExpr->tokenToString() + " " + criteriaArgument->toString(); + (*newItem)[COLUMN_ID_CRITERIA] = criteriaString; + } + d->dataTable->dataAwareObject()->insertItem(newItem, row_num); + //OK, row inserted: create a new set for it + KoProperty::Set &set = *createPropertySet( row_num, tableName, fieldName, true/*new one*/ ); + if (!columnAlias.isEmpty()) + set["alias"].setValue(columnAlias, false); + if (!criteriaString.isEmpty()) + set["criteria"].setValue( criteriaString, false ); + if (field->isExpression()) { +// (*newItem)[COLUMN_ID_COLUMN] = ; + if (!d->changeSingleCellValue(*newItem, COLUMN_ID_COLUMN, + QVariant(columnAlias + ": " + field->expression()->toString()), &result)) + return; //problems with setting column expression + } + } + + //4. show ORDER BY information + d->data->clearRowEditBuffer(); + KexiDB::OrderByColumnList &orderByColumns = query->orderByColumnList(); + QMap<KexiDB::QueryColumnInfo*,int> columnsOrder( + query->columnsOrder(KexiDB::QuerySchema::UnexpandedListWithoutAsterisks) ); + for (KexiDB::OrderByColumn::ListConstIterator orderByColumnsIt( orderByColumns.constBegin() ); + orderByColumnsIt!=orderByColumns.constEnd(); ++orderByColumnsIt) + { + KexiDB::QueryColumnInfo *column = (*orderByColumnsIt).column(); + KexiTableItem *rowItem = 0; + KoProperty::Set *rowPropertySet = 0; + if (column) { + //sorting for visible column + if (column->visible) { + if (columnsOrder.contains(column)) { + const int columnPosition = columnsOrder[ column ]; + rowItem = d->data->at( columnPosition ); + rowPropertySet = d->sets->at( columnPosition ); + kexipluginsdbg << "KexiQueryDesignerGuiEditor::showFieldsOrRelationsForQueryInternal():\n\t" + "Setting \"" << (*orderByColumnsIt).debugString() << "\" sorting for row #" + << columnPosition << endl; + } + } + } + else if ((*orderByColumnsIt).field()) { + //this will be presented as invisible field: create new row + field = (*orderByColumnsIt).field(); + QString tableName( field->table() ? field->table()->name() : QString::null ); + rowItem = createNewRow( tableName, field->name(), false /* !visible*/); + d->dataTable->dataAwareObject()->insertItem(rowItem, row_num); + rowPropertySet = createPropertySet( row_num, tableName, field->name(), true /*newOne*/ ); + propertySetSwitched(); + kexipluginsdbg << "KexiQueryDesignerGuiEditor::showFieldsOrRelationsForQueryInternal():\n\t" + "Setting \"" << (*orderByColumnsIt).debugString() << "\" sorting for invisible field " + << field->name() << ", table " << tableName << " -row #" << row_num << endl; + row_num++; + } + //alter sorting for either existing or new row + if (rowItem && rowPropertySet) { + d->data->updateRowEditBuffer(rowItem, COLUMN_ID_SORTING, + (*orderByColumnsIt).ascending() ? 1 : 2); // this will automatically update "sorting" property + // in slotBeforeCellChanged() + d->data->saveRowChanges(*rowItem, true); + (*rowPropertySet)["sorting"].clearModifiedFlag(); // this property should look "fresh" + if (!rowItem->at(COLUMN_ID_VISIBLE).toBool()) //update + (*rowPropertySet)["visible"].setValue(QVariant(false,0), false/*rememberOldValue*/); + } + } + + //5. Show fields for unused criterias (with "Visible" column set to false) + KexiDB::BaseExpr *criteriaArgument; // <-- contains field or table.field + for (QDictIterator<KexiDB::BaseExpr> it(criterias); (criteriaArgument = it.current()); ++it) { + if (usedCriterias[it.current()]) + continue; + //unused: append a new row + KexiDB::BinaryExpr *criteriaExpr = criteriaArgument->parent()->toBinary(); + if (!criteriaExpr) { + kexipluginswarn << "KexiQueryDesignerGuiEditor::showFieldsOrRelationsForQueryInternal(): " + "criteriaExpr is not a binary expr" << endl; + continue; + } + KexiDB::VariableExpr *columnNameArgument = criteriaExpr->left()->toVariable(); //left or right + if (!columnNameArgument) { + columnNameArgument = criteriaExpr->right()->toVariable(); + if (!columnNameArgument) { + kexipluginswarn << "KexiQueryDesignerGuiEditor::showFieldsOrRelationsForQueryInternal(): " + "columnNameArgument is not a variable (table or table.field) expr" << endl; + continue; + } + } + KexiDB::Field* field = 0; + if (-1 == columnNameArgument->name.find('.') && query->tables()->count()==1) { + //extreme case: only field name provided for one-table query: + field = query->tables()->first()->field(columnNameArgument->name); + } + else { + field = query->findTableField(columnNameArgument->name); + } + + if (!field) { + kexipluginswarn << "KexiQueryDesignerGuiEditor::showFieldsOrRelationsForQueryInternal(): " + "no columnInfo found in the query for name \"" << columnNameArgument->name << endl; + continue; + } + QString tableName, fieldName, columnAlias, criteriaString; +//! @todo what about ALIAS? + tableName = field->table()->name(); + fieldName = field->name(); + //create new row data + KexiTableItem *newItem = createNewRow(tableName, fieldName, false /* !visible*/); + if (criteriaExpr) { +//! @todo fix for !INFIX operators + if (criteriaExpr->token()=='=') + criteriaString = criteriaArgument->toString(); + else + criteriaString = criteriaExpr->tokenToString() + " " + criteriaArgument->toString(); + (*newItem)[COLUMN_ID_CRITERIA] = criteriaString; + } + d->dataTable->dataAwareObject()->insertItem(newItem, row_num); + //OK, row inserted: create a new set for it + KoProperty::Set &set = *createPropertySet( row_num++, tableName, fieldName, true/*new one*/ ); +//! @todo if (!columnAlias.isEmpty()) +//! @todo set["alias"].setValue(columnAlias, false); +//// if (!criteriaString.isEmpty()) + set["criteria"].setValue( criteriaString, false ); + set["visible"].setValue( QVariant(false,1), false ); + } + + //current property set has most probably changed + propertySetSwitched(); + + if (!was_dirty) + setDirty(false); + //move to 1st column, 1st row + d->dataTable->dataAwareObject()->ensureCellVisible(0,0); +// tempData()->registerTableSchemaChanges(query); +} + +bool KexiQueryDesignerGuiEditor::loadLayout() +{ + QString xml; +// if (!loadDataBlock( xml, "query_layout" )) { + loadDataBlock( xml, "query_layout" ); + //TODO errmsg +// return false; +// } + if (xml.isEmpty()) { + //in a case when query layout was not saved, build layout by hand + // -- dynamic cast because of a need for handling invalid queries + // (as in KexiQueryDesignerGuiEditor::afterSwitchFrom()): + KexiDB::QuerySchema * q = dynamic_cast<KexiDB::QuerySchema *>(parentDialog()->schemaData()); + if (q) { + showTablesForQuery( q ); + KexiDB::ResultInfo result; + showRelationsForQuery( q, result ); + if (!result.success) { + parentDialog()->setStatus(&result, i18n("Query definition loading failed.")); + return false; + } + } + return true; + } + + QDomDocument doc; + doc.setContent(xml); + QDomElement doc_el = doc.documentElement(), el; + if (doc_el.tagName()!="query_layout") { + //TODO errmsg + return false; + } + + const bool was_dirty = dirty(); + + //add tables and relations to the relation view + for (el = doc_el.firstChild().toElement(); !el.isNull(); el=el.nextSibling().toElement()) { + if (el.tagName()=="table") { + KexiDB::TableSchema *t = d->conn->tableSchema(el.attribute("name")); + int x = el.attribute("x","-1").toInt(); + int y = el.attribute("y","-1").toInt(); + int width = el.attribute("width","-1").toInt(); + int height = el.attribute("height","-1").toInt(); + QRect rect; + if (x!=-1 || y!=-1 || width!=-1 || height!=-1) + rect = QRect(x,y,width,height); + d->relations->addTable( t, rect ); + } + else if (el.tagName()=="conn") { + SourceConnection src_conn; + src_conn.masterTable = el.attribute("mtable"); + src_conn.masterField = el.attribute("mfield"); + src_conn.detailsTable = el.attribute("dtable"); + src_conn.detailsField = el.attribute("dfield"); + d->relations->addConnection(src_conn); + } + } + + if (!was_dirty) + setDirty(false); + return true; +} + +bool KexiQueryDesignerGuiEditor::storeLayout() +{ + KexiQueryPart::TempData * temp = tempData(); + + // Save SQL without driver-escaped keywords. + KexiDB::Connection* dbConn = mainWin()->project()->dbConnection(); + if (m_dialog->schemaData()) //set this instance as obsolete (only if it's stored) + dbConn->setQuerySchemaObsolete( m_dialog->schemaData()->name() ); + + KexiDB::Connection::SelectStatementOptions options; + options.identifierEscaping = KexiDB::Driver::EscapeKexi|KexiDB::Driver::EscapeAsNecessary; + options.addVisibleLookupColumns = false; + QString sqlText = dbConn->selectStatement( *temp->query(), options ); + if (!storeDataBlock( sqlText, "sql" )) { + return false; + } + + //serialize detailed XML query definition + QString xml = "<query_layout>", tmp; + for (TablesDictIterator it(*d->relations->tables()); it.current(); ++it) { + KexiRelationViewTableContainer *table_cont = it.current(); +/*! @todo what about query? */ + tmp = QString("<table name=\"")+QString(table_cont->schema()->name())+"\" x=\"" + +QString::number(table_cont->x()) + +"\" y=\""+QString::number(table_cont->y()) + +"\" width=\""+QString::number(table_cont->width()) + +"\" height=\""+QString::number(table_cont->height()) + +"\"/>"; + xml += tmp; + } + + KexiRelationViewConnection *con; + for (ConnectionListIterator it(*d->relations->connections()); (con = it.current()); ++it) { + tmp = QString("<conn mtable=\"") + QString(con->masterTable()->schema()->name()) + + "\" mfield=\"" + con->masterField() + "\" dtable=\"" + + QString(con->detailsTable()->schema()->name()) + + "\" dfield=\"" + con->detailsField() + "\"/>"; + xml += tmp; + } + xml += "</query_layout>"; + if (!storeDataBlock( xml, "query_layout" )) { + return false; + } + +// mainWin()->project()->reloadPartItem( m_dialog ); + + return true; +} + +QSize KexiQueryDesignerGuiEditor::sizeHint() const +{ + QSize s1 = d->relations->sizeHint(); + QSize s2 = d->head->sizeHint(); + return QSize(QMAX(s1.width(),s2.width()), s1.height()+s2.height()); +} + +KexiTableItem* +KexiQueryDesignerGuiEditor::createNewRow(const QString& tableName, const QString& fieldName, + bool visible) const +{ + KexiTableItem *newItem = d->data->createItem(); + QString key; + if (tableName=="*") + key="*"; + else { + if (!tableName.isEmpty()) + key = (tableName+"."); + key += fieldName; + } + (*newItem)[COLUMN_ID_COLUMN]=key; + (*newItem)[COLUMN_ID_TABLE]=tableName; + (*newItem)[COLUMN_ID_VISIBLE]=QVariant(visible, 1); +#ifndef KEXI_NO_QUERY_TOTALS + (*newItem)[COLUMN_ID_TOTALS]=QVariant(0); +#endif + return newItem; +} + +void KexiQueryDesignerGuiEditor::slotDragOverTableRow( + KexiTableItem * /*item*/, int /*row*/, QDragMoveEvent* e) +{ + if (e->provides("kexi/field")) { + e->acceptAction(true); + } +} + +void +KexiQueryDesignerGuiEditor::slotDroppedAtRow(KexiTableItem * /*item*/, int /*row*/, + QDropEvent *ev, KexiTableItem*& newItem) +{ + QString sourceMimeType; + QString srcTable; + QString srcField; + + if (!KexiFieldDrag::decodeSingle(ev,sourceMimeType,srcTable,srcField)) + return; + //insert new row at specific place + newItem = createNewRow(srcTable, srcField, true /* visible*/); + d->droppedNewItem = newItem; + d->droppedNewTable = srcTable; + d->droppedNewField = srcField; + //TODO +} + +void KexiQueryDesignerGuiEditor::slotNewItemAppendedForAfterDeletingInSpreadSheetMode() +{ + KexiTableItem *item = d->data->last(); + if (item) + item->at(COLUMN_ID_VISIBLE) = QVariant(false, 0); //the same init as in initTableRows() +} + +void KexiQueryDesignerGuiEditor::slotRowInserted(KexiTableItem* item, uint row, bool /*repaint*/) +{ + if (d->droppedNewItem && d->droppedNewItem==item) { + createPropertySet( row, d->droppedNewTable, d->droppedNewField, true ); + propertySetSwitched(); + d->droppedNewItem=0; + } +} + +void KexiQueryDesignerGuiEditor::slotTableAdded(KexiDB::TableSchema & /*t*/) +{ + if (!d->slotTableAdded_enabled) + return; + updateColumnsData(); + setDirty(); + d->dataTable->setFocus(); +} + +void KexiQueryDesignerGuiEditor::slotTableHidden(KexiDB::TableSchema & /*t*/) +{ + updateColumnsData(); + setDirty(); +} + +/*! @internal generates smallest unique alias */ +QCString KexiQueryDesignerGuiEditor::generateUniqueAlias() const +{ +//TODO: add option for using non-i18n'd "expr" prefix? + const QCString expStr + = i18n("short for 'expression' word (only latin letters, please)", "expr").latin1(); +//TODO: optimization: cache it? + QAsciiDict<char> aliases(101); + for (int r = 0; r<(int)d->sets->size(); r++) { + KoProperty::Set *set = d->sets->at(r); + if (set) { + const QCString a = (*set)["alias"].value().toCString().lower(); + if (!a.isEmpty()) + aliases.insert(a,(char*)1); + } + } + int aliasNr=1; + for (;;aliasNr++) { + if (!aliases[expStr+QString::number(aliasNr).latin1()]) + break; + } + return expStr+QString::number(aliasNr).latin1(); +} + +//! @todo this is primitive, temporary: reuse SQL parser +KexiDB::BaseExpr* +KexiQueryDesignerGuiEditor::parseExpressionString(const QString& fullString, int& token, + bool allowRelationalOperator) +{ + QString str = fullString.stripWhiteSpace(); + int len = 0; + //KexiDB::BaseExpr *expr = 0; + //1. get token + token = 0; + //2-char-long tokens + if (str.startsWith(">=")) + token = GREATER_OR_EQUAL; + else if (str.startsWith("<=")) + token = LESS_OR_EQUAL; + else if (str.startsWith("<>")) + token = NOT_EQUAL; + else if (str.startsWith("!=")) + token = NOT_EQUAL2; + else if (str.startsWith("==")) + token = '='; + + if (token!=0) + len = 2; + else if (str.startsWith("=") //1-char-long tokens + || str.startsWith("<") + || str.startsWith(">")) + { + token = str[0].latin1(); + len = 1; + } + else { + if (allowRelationalOperator) + token = '='; + } + + if (!allowRelationalOperator && token!=0) + return 0; + + //1. get expression after token + if (len>0) + str = str.mid(len).stripWhiteSpace(); + if (str.isEmpty()) + return 0; + + KexiDB::BaseExpr *valueExpr = 0; + QRegExp re; + if (str.length()>=2 && + ( + (str.startsWith("\"") && str.endsWith("\"")) + || (str.startsWith("'") && str.endsWith("'"))) + ) + { + valueExpr = new KexiDB::ConstExpr(CHARACTER_STRING_LITERAL, str.mid(1,str.length()-2)); + } + else if (str.startsWith("[") && str.endsWith("]")) { + valueExpr = new KexiDB::QueryParameterExpr(str.mid(1,str.length()-2)); + } + else if ((re = QRegExp("(\\d{1,4})-(\\d{1,2})-(\\d{1,2})")).exactMatch( str )) + { + valueExpr = new KexiDB::ConstExpr(DATE_CONST, QDate::fromString( + re.cap(1).rightJustify(4, '0')+"-"+re.cap(2).rightJustify(2, '0') + +"-"+re.cap(3).rightJustify(2, '0'), Qt::ISODate)); + } + else if ((re = QRegExp("(\\d{1,2}):(\\d{1,2})")).exactMatch( str ) + || (re = QRegExp("(\\d{1,2}):(\\d{1,2}):(\\d{1,2})")).exactMatch( str )) + { + QString res = re.cap(1).rightJustify(2, '0')+":"+re.cap(2).rightJustify(2, '0') + +":"+re.cap(3).rightJustify(2, '0'); +// kexipluginsdbg << res << endl; + valueExpr = new KexiDB::ConstExpr(TIME_CONST, QTime::fromString(res, Qt::ISODate)); + } + else if ((re = QRegExp("(\\d{1,4})-(\\d{1,2})-(\\d{1,2})\\s+(\\d{1,2}):(\\d{1,2})")).exactMatch( str ) + || (re = QRegExp("(\\d{1,4})-(\\d{1,2})-(\\d{1,2})\\s+(\\d{1,2}):(\\d{1,2}):(\\d{1,2})")).exactMatch( str )) + { + QString res = re.cap(1).rightJustify(4, '0')+"-"+re.cap(2).rightJustify(2, '0') + +"-"+re.cap(3).rightJustify(2, '0') + +"T"+re.cap(4).rightJustify(2, '0')+":"+re.cap(5).rightJustify(2, '0') + +":"+re.cap(6).rightJustify(2, '0'); +// kexipluginsdbg << res << endl; + valueExpr = new KexiDB::ConstExpr(DATETIME_CONST, + QDateTime::fromString(res, Qt::ISODate)); + } + else if (str[0]>='0' && str[0]<='9' || str[0]=='-' || str[0]=='+') { + //number + QString decimalSym = KGlobal::locale()->decimalSymbol(); + bool ok; + int pos = str.find('.'); + if (pos==-1) {//second chance: local decimal symbol + pos = str.find(decimalSym); + } + if (pos>=0) {//real const number + const int left = str.left(pos).toInt(&ok); + if (!ok) + return 0; + const int right = str.mid(pos+1).toInt(&ok); + if (!ok) + return 0; + valueExpr = new KexiDB::ConstExpr(REAL_CONST, QPoint(left,right)); //decoded to QPoint + } + else { + //integer const + const Q_LLONG val = str.toLongLong(&ok); + if (!ok) + return 0; + valueExpr = new KexiDB::ConstExpr(INTEGER_CONST, val); + } + } + else if (str.lower()=="null") { + valueExpr = new KexiDB::ConstExpr(SQL_NULL, QVariant()); + } + else {//identfier + if (!KexiUtils::isIdentifier(str)) + return 0; + valueExpr = new KexiDB::VariableExpr(str); + //find first matching field for name 'str': + for (TablesDictIterator it(*d->relations->tables()); it.current(); ++it) { +/*! @todo what about query? */ + if (it.current()->schema()->table() && it.current()->schema()->table()->field(str)) { + valueExpr->toVariable()->field = it.current()->schema()->table()->field(str); + break; + } + } + } + return valueExpr; +} + +void KexiQueryDesignerGuiEditor::slotBeforeCellChanged(KexiTableItem *item, int colnum, + QVariant& newValue, KexiDB::ResultInfo* result) +{ + if (colnum == COLUMN_ID_COLUMN) { + if (newValue.isNull()) { + d->data->updateRowEditBuffer(item, COLUMN_ID_TABLE, QVariant(), false/*!allowSignals*/); + d->data->updateRowEditBuffer(item, COLUMN_ID_VISIBLE, QVariant(false,1));//invisible + d->data->updateRowEditBuffer(item, COLUMN_ID_SORTING, QVariant()); +#ifndef KEXI_NO_QUERY_TOTALS + d->data->updateRowEditBuffer(item, COLUMN_ID_TOTALS, QVariant());//remove totals +#endif + d->data->updateRowEditBuffer(item, COLUMN_ID_CRITERIA, QVariant());//remove crit. + d->sets->removeCurrentPropertySet(); + } + else { + //auto fill 'table' column + QString fieldId( newValue.toString().stripWhiteSpace() ); //tmp, can look like "table.field" + QString fieldName; //"field" part of "table.field" or expression string + QString tableName; //empty for expressions + QCString alias; + QString columnValueForExpr; //for setting pretty printed "alias: expr" in 1st column + const bool isExpression = !d->fieldColumnIdentifiers[fieldId]; + if (isExpression) { + //this value is entered by hand and doesn't match + //any value in the combo box -- we're assuming this is an expression + //-table remains null + //-find "alias" in something like "alias : expr" + const int id = fieldId.find(':'); + if (id>0) { + alias = fieldId.left(id).stripWhiteSpace().latin1(); + if (!KexiUtils::isIdentifier(alias)) { + result->success = false; + result->allowToDiscardChanges = true; + result->column = colnum; + result->msg = i18n("Entered column alias \"%1\" is not a valid identifier.") + .arg(alias); + result->desc = i18n("Identifiers should start with a letter or '_' character"); + return; + } + } + fieldName = fieldId.mid(id+1).stripWhiteSpace(); + //check expr. + KexiDB::BaseExpr *e; + int dummyToken; + if ((e = parseExpressionString(fieldName, dummyToken, false/*allowRelationalOperator*/))) + { + fieldName = e->toString(); //print it prettier + //this is just checking: destroy expr. object + delete e; + } + else { + result->success = false; + result->allowToDiscardChanges = true; + result->column = colnum; + result->msg = i18n("Invalid expression \"%1\"").arg(fieldName); + return; + } + } + else {//not expr. + //this value is properly selected from combo box list + if (fieldId=="*") { + tableName = "*"; + } + else { + if (!KexiDB::splitToTableAndFieldParts( + fieldId, tableName, fieldName, KexiDB::SetFieldNameIfNoTableName)) + { + kexipluginswarn << "KexiQueryDesignerGuiEditor::slotBeforeCellChanged(): no 'field' or 'table.field'" << endl; + return; + } + } + } + bool saveOldValue = true; + KoProperty::Set *set = d->sets->findPropertySetForItem(*item); //*propertyBuffer(); + if (!set) { + saveOldValue = false; // no old val. + const int row = d->data->findRef(item); + if (row<0) { + result->success = false; + return; + } + set = createPropertySet( row, tableName, fieldName, true ); + propertySetSwitched(); + } + d->data->updateRowEditBuffer(item, COLUMN_ID_TABLE, QVariant(tableName), false/*!allowSignals*/); + d->data->updateRowEditBuffer(item, COLUMN_ID_VISIBLE, QVariant(true,1)); +#ifndef KEXI_NO_QUERY_TOTALS + d->data->updateRowEditBuffer(item, COLUMN_ID_TOTALS, QVariant(0)); +#endif + if (!sortingAllowed(fieldName, tableName)) { + // sorting is not available for "*" or "table.*" rows +//! @todo what about expressions? + d->data->updateRowEditBuffer(item, COLUMN_ID_SORTING, QVariant()); + } + //update properties + (*set)["field"].setValue(fieldName, saveOldValue); + if (isExpression) { + //-no alias but it's needed: + if (alias.isEmpty()) //-try oto get old alias + alias = (*set)["alias"].value().toCString(); + if (alias.isEmpty()) //-generate smallest unique alias + alias = generateUniqueAlias(); + } + (*set)["isExpression"].setValue(QVariant(isExpression,1), saveOldValue); + if (!alias.isEmpty()) { + (*set)["alias"].setValue(alias, saveOldValue); + //pretty printed "alias: expr" + newValue = QString(alias) + ": " + fieldName; + } + (*set)["caption"].setValue(QString::null, saveOldValue); + (*set)["table"].setValue(tableName, saveOldValue); + updatePropertiesVisibility(*set); + } + } + else if (colnum==COLUMN_ID_TABLE) { + if (newValue.isNull()) { + if (!item->at(COLUMN_ID_COLUMN).toString().isEmpty()) + d->data->updateRowEditBuffer(item, COLUMN_ID_COLUMN, QVariant(), false/*!allowSignals*/); + d->data->updateRowEditBuffer(item, COLUMN_ID_VISIBLE, QVariant(false,1));//invisible +#ifndef KEXI_NO_QUERY_TOTALS + d->data->updateRowEditBuffer(item, COLUMN_ID_TOTALS, QVariant());//remove totals +#endif + d->data->updateRowEditBuffer(item, COLUMN_ID_CRITERIA, QVariant());//remove crit. + d->sets->removeCurrentPropertySet(); + } + //update property + KoProperty::Set *set = d->sets->findPropertySetForItem(*item); + if (set) { + if ((*set)["isExpression"].value().toBool()==false) { + (*set)["table"] = newValue; + (*set)["caption"] = QString::null; + } + else { + //do not set table for expr. columns + newValue = QVariant(); + } +// KoProperty::Set &set = *propertyBuffer(); + updatePropertiesVisibility(*set); + } + } + else if (colnum==COLUMN_ID_VISIBLE) { + bool saveOldValue = true; + if (!propertySet()) { + saveOldValue = false; + createPropertySet( d->dataTable->dataAwareObject()->currentRow(), + item->at(COLUMN_ID_TABLE).toString(), item->at(COLUMN_ID_COLUMN).toString(), true ); +#ifndef KEXI_NO_QUERY_TOTALS + d->data->updateRowEditBuffer(item, COLUMN_ID_TOTALS, QVariant(0));//totals +#endif + propertySetSwitched(); + } + KoProperty::Set &set = *propertySet(); + set["visible"].setValue(newValue, saveOldValue); + } +#ifndef KEXI_NO_QUERY_TOTALS + else if (colnum==COLUMN_ID_TOTALS) { + //TODO: + //unused yet + setDirty(true); + } +#endif + else if (colnum==COLUMN_ID_SORTING) { + KoProperty::Set *set = d->sets->findPropertySetForItem(*item); + QString table( set->property("table").value().toString() ); + QString field( set->property("field").value().toString() ); + if (newValue.toInt()==0 || sortingAllowed(field, table)) { + KoProperty::Property &property = set->property("sorting"); + QString key( property.listData()->keysAsStringList()[ newValue.toInt() ] ); + kexipluginsdbg << "new key=" << key << endl; + property.setValue(key, true); + } + else { //show msg: sorting is not available + result->success = false; + result->allowToDiscardChanges = true; + result->column = colnum; + result->msg = i18n("Could not set sorting for multiple columns (%1)") + .arg(table=="*" ? table : (table+".*")); + } + } + else if (colnum==COLUMN_ID_CRITERIA) { +//! @todo this is primitive, temporary: reuse SQL parser + QString operatorStr, argStr; + KexiDB::BaseExpr* e = 0; + const QString str = newValue.toString().stripWhiteSpace(); + int token; + QString field, table; + KoProperty::Set *set = d->sets->findPropertySetForItem(*item); + if (set) { + field = (*set)["field"].value().toString(); + table = (*set)["table"].value().toString(); + } + if (!str.isEmpty() && (!set || table=="*" || field.find("*")!=-1)) { + //asterisk found! criteria not allowed + result->success = false; + result->allowToDiscardChanges = true; + result->column = colnum; + if (propertySet()) + result->msg = i18n("Could not set criteria for \"%1\"") + .arg(table=="*" ? table : field); + else + result->msg = i18n("Could not set criteria for empty row"); + //moved to result->allowToDiscardChanges handler //d->dataTable->dataAwareObject()->cancelEditor(); //prevents further editing of this cell + } + else if (str.isEmpty() || (e = parseExpressionString(str, token, true/*allowRelationalOperator*/))) + { + if (e) { + QString tokenStr; + if (token!='=') { + KexiDB::BinaryExpr be(KexiDBExpr_Relational, 0, token, 0); + tokenStr = be.tokenToString() + " "; + } + (*set)["criteria"] = tokenStr + e->toString(); //print it prettier + //this is just checking: destroy expr. object + delete e; + } + else if (str.isEmpty()) { + (*set)["criteria"] = QVariant(); //clear it + } + setDirty(true); + } + else { + result->success = false; + result->allowToDiscardChanges = true; + result->column = colnum; + result->msg = i18n("Invalid criteria \"%1\"").arg(newValue.toString()); + } + } +} + +void KexiQueryDesignerGuiEditor::slotTablePositionChanged(KexiRelationViewTableContainer*) +{ + setDirty(true); +} + +void KexiQueryDesignerGuiEditor::slotAboutConnectionRemove(KexiRelationViewConnection*) +{ + setDirty(true); +} + +void KexiQueryDesignerGuiEditor::slotTableFieldDoubleClicked( + KexiDB::TableSchema* table, const QString& fieldName ) +{ + if (!table || (!table->field(fieldName) && fieldName!="*")) + return; + int row_num; + //find last filled row in the GUI table + for (row_num=d->sets->size()-1; row_num>=0 && !d->sets->at(row_num); row_num--) + ; + row_num++; //after + //add row + KexiTableItem *newItem = createNewRow(table->name(), fieldName, true /* visible*/); + d->dataTable->dataAwareObject()->insertItem(newItem, row_num); + d->dataTable->dataAwareObject()->setCursorPosition(row_num, 0); + //create buffer + createPropertySet( row_num, table->name(), fieldName, true/*new one*/ ); + propertySetSwitched(); + d->dataTable->setFocus(); +} + +KoProperty::Set *KexiQueryDesignerGuiEditor::propertySet() +{ + return d->sets->currentPropertySet(); +} + +void KexiQueryDesignerGuiEditor::updatePropertiesVisibility(KoProperty::Set& set) +{ + const bool asterisk = isAsterisk( + set["table"].value().toString(), set["field"].value().toString() + ); +#ifndef KEXI_NO_UNFINISHED + set["caption"].setVisible( !asterisk ); +#endif + set["alias"].setVisible( !asterisk ); +/*always invisible #ifndef KEXI_NO_UNFINISHED + set["sorting"].setVisible( !asterisk ); +#endif*/ + propertySetReloaded(true); +} + +KoProperty::Set* +KexiQueryDesignerGuiEditor::createPropertySet( int row, + const QString& tableName, const QString& fieldName, bool newOne ) +{ + //const bool asterisk = isAsterisk(tableName, fieldName); + QString typeName = "KexiQueryDesignerGuiEditor::Column"; + KoProperty::Set *set = new KoProperty::Set(d->sets, typeName); + KoProperty::Property *prop; + + //meta-info for property editor + set->addProperty(prop = new KoProperty::Property("this:classString", i18n("Query column")) ); + prop->setVisible(false); +//! \todo add table_field icon (add buff->addProperty(prop = new KexiProperty("this:iconName", "table_field") ); +// prop->setVisible(false); + + set->addProperty(prop = new KoProperty::Property("table", QVariant(tableName)) ); + prop->setVisible(false);//always hidden + + set->addProperty(prop = new KoProperty::Property("field", QVariant(fieldName)) ); + prop->setVisible(false);//always hidden + + set->addProperty(prop = new KoProperty::Property("caption", QVariant(QString::null), i18n("Caption") ) ); +#ifdef KEXI_NO_UNFINISHED + prop->setVisible(false); +#endif + + set->addProperty(prop = new KoProperty::Property("alias", QVariant(QString::null), i18n("Alias")) ); + + set->addProperty(prop = new KoProperty::Property("visible", QVariant(true, 4)) ); + prop->setVisible(false); + +/*TODO: + set->addProperty(prop = new KexiProperty("totals", QVariant(QString::null)) ); + prop->setVisible(false);*/ + + //sorting + QStringList slist, nlist; + slist << "nosorting" << "ascending" << "descending"; + nlist << i18n("None") << i18n("Ascending") << i18n("Descending"); + set->addProperty(prop = new KoProperty::Property("sorting", + slist, nlist, *slist.at(0), i18n("Sorting"))); + prop->setVisible(false); + + set->addProperty(prop = new KoProperty::Property("criteria", QVariant(QString::null)) ); + prop->setVisible(false); + + set->addProperty(prop = new KoProperty::Property("isExpression", QVariant(false, 1)) ); + prop->setVisible(false); + + connect(set, SIGNAL(propertyChanged(KoProperty::Set&, KoProperty::Property&)), + this, SLOT(slotPropertyChanged(KoProperty::Set&, KoProperty::Property&))); + + d->sets->insert(row, set, newOne); + + updatePropertiesVisibility(*set); + return set; +} + +void KexiQueryDesignerGuiEditor::setFocus() +{ + d->dataTable->setFocus(); +} + +void KexiQueryDesignerGuiEditor::slotPropertyChanged(KoProperty::Set& set, KoProperty::Property& property) +{ + const QCString& pname = property.name(); +/* + * TODO (js) use KexiProperty::setValidator(QString) when implemented as described in TODO #60 + */ + if (pname=="alias" || pname=="name") { + const QVariant& v = property.value(); + if (!v.toString().stripWhiteSpace().isEmpty() && !KexiUtils::isIdentifier( v.toString() )) { + KMessageBox::sorry(this, + KexiUtils::identifierExpectedMessage(property.caption(), v.toString())); + property.resetValue(); + } + if (pname=="alias") { + if (set["isExpression"].value().toBool()==true) { + //update value in column #1 + d->dataTable->dataAwareObject()->acceptEditor(); +// d->dataTable->dataAwareObject()->setCursorPosition(d->dataTable->dataAwareObject()->currentRow(),0); + //d->dataTable->dataAwareObject()->startEditCurrentCell(); + d->data->updateRowEditBuffer(d->dataTable->dataAwareObject()->selectedItem(), + 0, QVariant(set["alias"].value().toString() + ": " + set["field"].value().toString())); + d->data->saveRowChanges(*d->dataTable->dataAwareObject()->selectedItem(), true); +// d->dataTable->dataAwareObject()->acceptRowEdit(); + } + } + } +} + +void KexiQueryDesignerGuiEditor::slotNewItemStored(KexiPart::Item& item) +{ + d->relations->objectCreated(item.mimeType(), item.name().latin1()); +} + +void KexiQueryDesignerGuiEditor::slotItemRemoved(const KexiPart::Item& item) +{ + d->relations->objectDeleted(item.mimeType(), item.name().latin1()); +} + +void KexiQueryDesignerGuiEditor::slotItemRenamed(const KexiPart::Item& item, const QCString& oldName) +{ + d->relations->objectRenamed(item.mimeType(), oldName, item.name().latin1()); +} + +#include "kexiquerydesignerguieditor.moc" + diff --git a/kexi/plugins/queries/kexiquerydesignerguieditor.h b/kexi/plugins/queries/kexiquerydesignerguieditor.h new file mode 100644 index 00000000..03acb7f6 --- /dev/null +++ b/kexi/plugins/queries/kexiquerydesignerguieditor.h @@ -0,0 +1,170 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Lucijan Busch <lucijan@kde.org> + Copyright (C) 2004-2006 Jaroslaw Staniek <js@iidea.pl> + + This library 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 library 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 library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIQUERYDESIGNERGUIEDITOR_H +#define KEXIQUERYDESIGNERGUIEDITOR_H + +#include <qguardedptr.h> +#include <qsplitter.h> + +#include <kexiviewbase.h> +#include "kexiquerypart.h" + +class KexiMainWindow; +class KexiTableViewData; +class KexiDataTable; +class KexiTableItem; +class KexiRelationWidget; +class KexiSectionHeader; +class KexiDataAwarePropertySet; +class KexiRelationViewTableContainer; +class KexiRelationViewConnection; + +namespace KexiPart +{ + class Item; +} + +namespace KoProperty { + class Property; + class Set; +} + +namespace KexiDB +{ + class Connection; + class QuerySchema; + class TableSchema; + class ResultInfo; +} + +//! Design view of the Query Designer +class KexiQueryDesignerGuiEditor : public KexiViewBase +{ + Q_OBJECT + + public: + KexiQueryDesignerGuiEditor(KexiMainWindow *mainWin, QWidget *parent, const char *name = 0); + virtual ~KexiQueryDesignerGuiEditor(); + +// KexiDB::QuerySchema *schema(); + + KexiRelationWidget *relationView() const; + + virtual QSize sizeHint() const; + + public slots: + virtual void setFocus(); + + protected: + void initTableColumns(); //!< Called just once. + void initTableRows(); //!< Called to have all rows empty. +//unused void addRow(const QString &tbl, const QString &field); +// void restore(); + virtual tristate beforeSwitchTo(int mode, bool &dontStore); + virtual tristate afterSwitchFrom(int mode); + + virtual KexiDB::SchemaData* storeNewData(const KexiDB::SchemaData& sdata, bool &cancel); + virtual tristate storeData(bool dontAsk = false); + + /*! Updates data in columns depending on tables that are currently inserted. + Tabular Data in combo box popups is updated as well. */ + void updateColumnsData(); + + /*! \return property buffer associated with currently selected row (i.e. field) + or 0 if current row is empty. */ + virtual KoProperty::Set *propertySet(); + + KoProperty::Set* createPropertySet( int row, + const QString& tableName, const QString& fieldName, bool newOne = false ); + + /*! Builds query schema out of information provided by gui. + The schema is stored in temp->query member. + \a errMsg is optional error message returned. + \return true on proper schema creation. */ + bool buildSchema(QString *errMsg = 0); + + KexiQueryPart::TempData * tempData() const; + + /*! Helper: allocates and initializes new table view's row. Doesn't insert it, just returns. + \a tableName and \a fieldName shoudl be provided. + \a visible flag sets value for "Visible" column. */ + KexiTableItem* createNewRow(const QString& tableName, const QString& fieldName, + bool visible) const; + + KexiDB::BaseExpr* parseExpressionString(const QString& fullString, int& token, + bool allowRelationalOperator); + + QCString generateUniqueAlias() const; + void updatePropertiesVisibility(KoProperty::Set& buf); + + protected slots: + void slotDragOverTableRow(KexiTableItem *item, int row, QDragMoveEvent* e); + void slotDroppedAtRow(KexiTableItem *item, int row, + QDropEvent *ev, KexiTableItem*& newItem); + //! Reaction on appending a new item after deleting one + void slotNewItemAppendedForAfterDeletingInSpreadSheetMode(); + void slotTableAdded(KexiDB::TableSchema &t); + void slotTableHidden(KexiDB::TableSchema &t); + + //! Called before cell change in tableview. + void slotBeforeCellChanged(KexiTableItem *item, int colnum, + QVariant& newValue, KexiDB::ResultInfo* result); + + void slotRowInserted(KexiTableItem* item, uint row, bool repaint); + void slotTablePositionChanged(KexiRelationViewTableContainer*); + void slotAboutConnectionRemove(KexiRelationViewConnection*); + void slotTableFieldDoubleClicked( KexiDB::TableSchema* table, const QString& fieldName ); + + /*! Loads layout of relation GUI diagram. */ + bool loadLayout(); + + /*! Stores layout of relation GUI diagram. */ + bool storeLayout(); + + void showTablesForQuery(KexiDB::QuerySchema *query); + //! @internal + void showFieldsOrRelationsForQueryInternal( + KexiDB::QuerySchema *query, bool showFields, bool showRelations, KexiDB::ResultInfo& result); + //! convenience method equal to showFieldsOrRelationsForQueryInternal(query, true, true) + void showFieldsAndRelationsForQuery(KexiDB::QuerySchema *query, KexiDB::ResultInfo& result); + //! convenience method equal to showFieldsOrRelationsForQueryInternal(query, true, false) + void showFieldsForQuery(KexiDB::QuerySchema *query, KexiDB::ResultInfo& result); + //! convenience method equal to showFieldsOrRelationsForQueryInternal(query, false, true) + void showRelationsForQuery(KexiDB::QuerySchema *query, KexiDB::ResultInfo& result); + + void addConnection(KexiDB::Field *masterField, KexiDB::Field *detailsField); + + void slotPropertyChanged(KoProperty::Set& list, KoProperty::Property& property); + +// void slotObjectCreated(const QCString &mime, const QCString& name); + void slotNewItemStored(KexiPart::Item&); + void slotItemRemoved(const KexiPart::Item& item); + void slotItemRenamed(const KexiPart::Item& item, const QCString& oldName); + + private: + class Private; + Private *d; + + friend class KexiQueryView; // for storeNewData() and storeData() only +}; + +#endif + 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 <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 <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" + diff --git a/kexi/plugins/queries/kexiquerydesignersql.h b/kexi/plugins/queries/kexiquerydesignersql.h new file mode 100644 index 00000000..f31c838f --- /dev/null +++ b/kexi/plugins/queries/kexiquerydesignersql.h @@ -0,0 +1,82 @@ +/* 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. +*/ + +#ifndef KEXIQUERYDESIGNERSQL_H +#define KEXIQUERYDESIGNERSQL_H + +#include <kexiviewbase.h> +#include "kexiquerypart.h" + +class KexiQueryDesignerSQLEditor; +class KexiQueryDesignerSQLViewPrivate; + +//! The KexiQueryDesignerSQLView class for editing Queries in text mode. +/*! It is a view containing SQL text editor + and SQL history/status widget splitted vertically. + Depending on user's will, the widget can be in "sql history" + mode or in "sql status" mode. */ +class KexiQueryDesignerSQLView : public KexiViewBase +{ + Q_OBJECT + + public: + KexiQueryDesignerSQLView(KexiMainWindow *mainWin, QWidget *parent, const char *name = 0); + virtual ~KexiQueryDesignerSQLView(); + + QString sqlText() const; + KexiQueryDesignerSQLEditor *editor() const; + + virtual bool eventFilter ( QObject *o, QEvent *e ); + + protected: + KexiQueryPart::TempData * tempData() const; + + virtual tristate beforeSwitchTo(int mode, bool &dontStore); + virtual tristate afterSwitchFrom(int mode); + virtual KexiDB::SchemaData* storeNewData(const KexiDB::SchemaData& sdata, bool &cancel); + virtual tristate storeData(bool dontAsk = false); + + void setStatusOk(); + void setStatusError(const QString& msg); + void setStatusEmpty(); + void setStatusText(const QString& text); + + virtual void updateActions(bool activated); + + protected slots: + /*! Performs query checking (by text parsing). \return true and sets d->parsedQuery + to the new query schema object on success. */ + bool slotCheckQuery(); + void slotUpdateMode(); + void slotTextChanged(); +// void slotHistoryHeaderButtonClicked(const QString& buttonIdentifier); + void slotSelectQuery(); + + signals: + void queryShortcut(); + + private: + class Private; + Private *d; + + friend class KexiQueryView; // for storeNewData() and storeData() only +}; + +#endif diff --git a/kexi/plugins/queries/kexiquerydesignersqlhistory.cpp b/kexi/plugins/queries/kexiquerydesignersqlhistory.cpp new file mode 100644 index 00000000..d86caf83 --- /dev/null +++ b/kexi/plugins/queries/kexiquerydesignersqlhistory.cpp @@ -0,0 +1,373 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Lucijan Busch <lucijan@kde.org> + Copyright (C) 2004 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 <qpainter.h> +#include <qclipboard.h> +#include <qregexp.h> + +#include <kpopupmenu.h> +#include <klocale.h> +#include <kiconloader.h> +#include <kdebug.h> +#include <kglobalsettings.h> +#include <kapplication.h> + +#include "kexiquerydesignersqlhistory.h" + +KexiQueryDesignerSQLHistory::KexiQueryDesignerSQLHistory(QWidget *parent, const char *name) + : QScrollView(parent, name) +{ + viewport()->setPaletteBackgroundColor(white); + + m_selected = 0; + m_history = new History(); + m_history->setAutoDelete(true); + + m_popup = new KPopupMenu(this); + m_popup->insertItem(SmallIcon("editcopy"), i18n("Copy to Clipboard"), this, SLOT(slotToClipboard())); +} + +KexiQueryDesignerSQLHistory::~KexiQueryDesignerSQLHistory() +{ +} + +void +KexiQueryDesignerSQLHistory::drawContents(QPainter *p, int cx, int cy, int cw, int ch) +{ + QRect clipping(cx, cy, cw, ch); + + int y = 0; + for(HistoryEntry *it = m_history->first(); it; it = m_history->next()) + { +// it->drawItem(p, visibleWidth()); + if(clipping.intersects(it->geometry(y, visibleWidth(), fontMetrics()))) + { + p->saveWorldMatrix(); + p->translate(0, y); + it->drawItem(p, visibleWidth(), colorGroup()); + p->restoreWorldMatrix(); + } + y += it->geometry(y, visibleWidth(), fontMetrics()).height() + 5; + } +} + +void +KexiQueryDesignerSQLHistory::contentsMousePressEvent(QMouseEvent * e) +{ + int y = 0; + HistoryEntry *popupHistory = 0; + int pos; + for(QPtrListIterator<HistoryEntry> it(*m_history); it.current(); ++it) + { + if(it.current()->isSelected()) + { + //clear + it.current()->setSelected(false, colorGroup()); + updateContents(it.current()->geometry(y, visibleWidth(), fontMetrics())); + } + + if(it.current()->geometry(y, visibleWidth(), fontMetrics()).contains(e->pos())) + { + popupHistory = it.current(); + pos = y; + } + y += it.current()->geometry(y, visibleWidth(), fontMetrics()).height() + 5; + } + + //now do update + if (popupHistory) { + if (m_selected && m_selected != popupHistory) { + m_selected->setSelected(false, colorGroup()); + updateContents(m_selected->geometry(pos, visibleWidth(), fontMetrics())); + } + m_selected = popupHistory; + m_selected->setSelected(true, colorGroup()); + updateContents(m_selected->geometry(pos, visibleWidth(), fontMetrics())); + if(e->button() == RightButton) { + m_popup->exec(e->globalPos()); + } + } +} + +void +KexiQueryDesignerSQLHistory::contentsMouseDoubleClickEvent(QMouseEvent * e) +{ + contentsMousePressEvent(e); + if (m_selected) + emit currentItemDoubleClicked(); +} + +void +KexiQueryDesignerSQLHistory::addEvent(const QString& q, bool s, const QString &error) +{ + HistoryEntry *he=m_history->last(); + if (he) { + if (he->statement()==q) { + he->updateTime(QTime::currentTime()); + repaint(); + return; + } + } + addEntry(new HistoryEntry(s, QTime::currentTime(), q, error)); +} + +void +KexiQueryDesignerSQLHistory::addEntry(HistoryEntry *e) +{ + m_history->append(e); +// m_history->prepend(e); + + int y = 0; + for(HistoryEntry *it = m_history->first(); it; it = m_history->next()) + { + y += it->geometry(y, visibleWidth(), fontMetrics()).height() + 5; + } + + resizeContents(visibleWidth() - 1, y); + if (m_selected) { + m_selected->setSelected(false, colorGroup()); + } + m_selected = e; + m_selected->setSelected(true, colorGroup()); + ensureVisible(0,y+5); + updateContents(); +/* ensureVisible(0, 0); + if (m_selected) { + m_selected->setSelected(false, colorGroup()); + } + m_selected = e; + m_selected->setSelected(true, colorGroup()); +// updateContents(); + updateContents(m_selected->geometry(0, visibleWidth(), fontMetrics()));*/ +} + +/*void +KexiQueryDesignerSQLHistory::contextMenu(const QPoint &pos, HistoryEntry *) +{ + KPopupMenu p(this); + p.insertItem(SmallIcon("editcopy"), i18n("Copy to Clipboard"), this, SLOT(slotToClipboard())); + + +#ifndef KEXI_NO_UNFINISHED + p.insertSeparator(); + p.insertItem(SmallIcon("edit"), i18n("Edit"), this, SLOT(slotEdit())); + p.insertItem(SmallIcon("reload"), i18n("Requery")); +#endif + + p.exec(pos); +}*/ + +void +KexiQueryDesignerSQLHistory::slotToClipboard() +{ + if(!m_selected) + return; + + QApplication::clipboard()->setText(m_selected->statement(), QClipboard::Clipboard); +} + +void +KexiQueryDesignerSQLHistory::slotEdit() +{ + emit editRequested(m_selected->statement()); +} + +QString +KexiQueryDesignerSQLHistory::selectedStatement() const +{ + return m_selected ? m_selected->statement() : QString::null; +} + +void +KexiQueryDesignerSQLHistory::setHistory(History *h) +{ + m_history = h; + update(); +} + +void KexiQueryDesignerSQLHistory::clear() +{ + m_selected = 0; + m_history->clear(); + updateContents(); +} + +KPopupMenu* KexiQueryDesignerSQLHistory::popupMenu() const +{ + return m_popup; +} + +//================================== + +HistoryEntry::HistoryEntry(bool succeed, const QTime &execTime, const QString &statement, /*int ,*/ const QString &err) +{ + m_succeed = succeed; + m_execTime = execTime; + m_statement = statement; + m_error = err; + m_selected = false; + highlight(QColorGroup()); +} + +void +HistoryEntry::drawItem(QPainter *p, int width, const QColorGroup &cg) +{ + p->setPen(QColor(200, 200, 200)); + p->setBrush(QColor(200, 200, 200)); + p->drawRect(2, 2, 200, 20); + p->setPen(QColor(0, 0, 0)); + + if(m_succeed) + p->drawPixmap(4, 4, SmallIcon("button_ok")); + else + p->drawPixmap(4, 4, SmallIcon("button_cancel")); + + p->drawText(22, 2, 180, 20, Qt::AlignLeft | Qt::AlignVCenter, m_execTime.toString()); + p->setPen(QColor(200, 200, 200)); + p->setBrush(QColor(255, 255, 255)); + m_formated->setWidth(width - 2); + QRect content(2, 21, width - 2, m_formated->height()); +// QRect content = p->fontMetrics().boundingRect(2, 21, width - 2, 0, Qt::WordBreak | Qt::AlignLeft | Qt::AlignVCenter, m_statement); +// QRect content(2, 21, width - 2, p->fontMetrics().height() + 4); +// content = QRect(2, 21, width - 2, m_for.height()); + + if(m_selected) + p->setBrush(cg.highlight()); + + p->drawRect(content); + + if(!m_selected) + p->setPen(cg.text()); + else + p->setPen(cg.highlightedText()); + + content.setX(content.x() + 2); + content.setWidth(content.width() - 2); +// p->drawText(content, Qt::WordBreak | Qt::AlignLeft | Qt::AlignVCenter, m_statement); + m_formated->draw(p, content.x(), content.y(), content, cg); +} + +void +HistoryEntry::highlight(const QColorGroup &cg) +{ + QString statement; + QString text; + bool quote = false; + bool dblquote = false; + + statement = m_statement; + statement.replace("<", "<"); + statement.replace(">", ">"); + statement.replace("\r\n", "<br>"); //(js) first win32 specific pair + statement.replace("\n", "<br>"); // now single \n + statement.replace(" ", " "); + statement.replace("\t", " "); + + // getting quoting... + if(!m_selected) + { + for(int i=0; i < (int)statement.length(); i++) + { + QString beginTag; + QString endTag; + QChar curr = QChar(statement[i]); + + if(curr == "'" && !dblquote && QChar(statement[i-1]) != "\\") + { + if(!quote) + { + quote = true; + beginTag += "<font color=\"#ff0000\">"; + } + else + { + quote = false; + endTag += "</font>"; + } + } + if(curr == "\"" && !quote && QChar(statement[i-1]) != "\\") + { + if(!dblquote) + { + dblquote = true; + beginTag += "<font color=\"#ff0000\">"; + } + else + { + dblquote = false; + endTag += "</font>"; + } + } + if(QRegExp("[0-9]").exactMatch(QString(curr)) && !quote && !dblquote) + { + beginTag += "<font color=\"#0000ff\">"; + endTag += "</font>"; + } + + text += beginTag + curr + endTag; + } + } + else + { + text = QString("<font color=\"%1\">%2").arg(cg.highlightedText().name()).arg(statement); + } + + QRegExp keywords("\\b(SELECT|UPDATE|INSERT|DELETE|DROP|FROM|WHERE|AND|OR|NOT|NULL|JOIN|LEFT|RIGHT|ON|INTO|TABLE)\\b"); + keywords.setCaseSensitive(false); + text = text.replace(keywords, "<b>\\1</b>"); + + if(!m_error.isEmpty()) +// text += ("<br>"+i18n("Error: %1").arg(m_error)); +// text += QString("<br><font face=\"") + KGlobalSettings::generalFont().family() + QString("\" size=\"-1\">") + i18n("Error: %1").arg(m_error) + "</font>"; + text += QString("<br><font face=\"") + KGlobalSettings::generalFont().family() + QString("\">") + i18n("Error: %1").arg(m_error) + "</font>"; + + kdDebug() << "HistoryEntry::highlight() text:" << text << endl; +// m_formated = new QSimpleRichText(text, QFont("courier", 8)); + m_formated = new QSimpleRichText(text, KGlobalSettings::fixedFont()); + +} + +void +HistoryEntry::setSelected(bool selected, const QColorGroup &cg) +{ + m_selected = selected; + highlight(cg); +} + +QRect +HistoryEntry::geometry(int y, int width, QFontMetrics f) +{ + Q_UNUSED( f ); + +// int h = 21 + f.boundingRect(2, 21, width - 2, 0, Qt::WordBreak | Qt::AlignLeft | Qt::AlignVCenter, m_statement).height(); +// return QRect(0, y, width, h); + m_formated->setWidth(width - 2); + return QRect(0, y, width, m_formated->height() + 21); +} + +void HistoryEntry::updateTime(const QTime &execTime) { + m_execTime=execTime; +} + +HistoryEntry::~HistoryEntry() +{ +} + +#include "kexiquerydesignersqlhistory.moc" diff --git a/kexi/plugins/queries/kexiquerydesignersqlhistory.h b/kexi/plugins/queries/kexiquerydesignersqlhistory.h new file mode 100644 index 00000000..a8d0c2e0 --- /dev/null +++ b/kexi/plugins/queries/kexiquerydesignersqlhistory.h @@ -0,0 +1,104 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Lucijan Busch <lucijan@kde.org> + Copyright (C) 2004 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. +*/ + +#ifndef KEXIQUERYDESIGNERSQLHISTORY_H +#define KEXIQUERYDESIGNERSQLHISTORY_H + +#include <qscrollview.h> +#include <qdatetime.h> +#include <qptrlist.h> +#include <qmap.h> +#include <qsimplerichtext.h> + +class QSimpleRichText; +class KPopupMenu; + +class HistoryEntry +{ + public: + HistoryEntry(bool success, const QTime &time, const QString &statement, /*int y,*/ const QString &error = QString::null); + ~HistoryEntry(); + + QRect geometry(int y, int width, QFontMetrics f); + void drawItem(QPainter *p, int width, const QColorGroup &cg); + + void setSelected(bool selected, const QColorGroup &cg); + bool isSelected() const { return m_selected; } + void highlight(const QColorGroup &selected); + + QString statement() { return m_statement; } + void updateTime(const QTime &execTime); + + private: + bool m_succeed; + QTime m_execTime; + QString m_statement; + QString m_error; + QSimpleRichText *m_formated; + + int m_y; + bool m_selected; +}; + +typedef QPtrList<HistoryEntry> History; + +class KexiQueryDesignerSQLHistory : public QScrollView +{ + Q_OBJECT + + public: + KexiQueryDesignerSQLHistory(QWidget *parent, const char *name=0); + virtual ~KexiQueryDesignerSQLHistory(); + + KPopupMenu* popupMenu() const; + +// void contextMenu(const QPoint &pos, HistoryEntry *e); + + void setHistory(History *h); + + QString selectedStatement() const; + + public slots: + void addEvent(const QString& q, bool s, const QString &error); + + void slotToClipboard(); + void slotEdit(); + + void clear(); + +// HistoryItem itemAt(int y); + + protected: + void addEntry(HistoryEntry *e); + virtual void drawContents(QPainter *p, int cx, int cy, int cw, int ch); + virtual void contentsMousePressEvent(QMouseEvent * e); + virtual void contentsMouseDoubleClickEvent(QMouseEvent * e); + + signals: + void editRequested(const QString &text); + void currentItemDoubleClicked(); + + private: + History *m_history; + HistoryEntry *m_selected; + KPopupMenu *m_popup; +}; + +#endif diff --git a/kexi/plugins/queries/kexiqueryhandler.desktop b/kexi/plugins/queries/kexiqueryhandler.desktop new file mode 100644 index 00000000..4a4f478e --- /dev/null +++ b/kexi/plugins/queries/kexiqueryhandler.desktop @@ -0,0 +1,111 @@ +[Desktop Entry] +Type=Service +ServiceTypes=Kexi/Handler + +GenericName=Queries +GenericName[bg]=Заявки +GenericName[ca]=Consultes +GenericName[cs]=Dotazy +GenericName[cy]=Ymholiadau +GenericName[da]=Forespørgsler +GenericName[de]=Abfragen +GenericName[el]=Ερωτήματα +GenericName[eo]=Serĉmendoj +GenericName[es]=Consultas +GenericName[et]=Päringud +GenericName[eu]=Kontsultak +GenericName[fa]=پرسوجوها +GenericName[fi]=Kyselyt +GenericName[fr]=Requêtes +GenericName[ga]=Iarratais +GenericName[gl]=Pesquisas +GenericName[he]=שאילתות +GenericName[hi]=क्वैरीज़ +GenericName[hr]=Upiti +GenericName[hu]=Lekérdezések +GenericName[is]=Fyrirspurnir +GenericName[it]=Interrogazioni +GenericName[ja]=クエリ +GenericName[km]=សំណួរ +GenericName[lt]=Užklausos +GenericName[lv]=Vaicājumi +GenericName[ms]=Pertanyaan +GenericName[nb]=Spørringer +GenericName[nds]=Affragen +GenericName[ne]=क्वेरीहरू +GenericName[nn]=Spørjingar +GenericName[pl]=Zapytania +GenericName[pt]=Pesquisas +GenericName[pt_BR]=Consultas +GenericName[ru]=Запросы +GenericName[se]=Jearahusat +GenericName[sk]=Otázky +GenericName[sl]=Poizvedbe +GenericName[sr]=Упити +GenericName[sr@Latn]=Upiti +GenericName[sv]=Förfrågningar +GenericName[ta]=கேள்விகள் +GenericName[tr]=Sorgular +GenericName[uk]=Запити +GenericName[uz]=Soʻrovlar +GenericName[uz@cyrillic]=Сўровлар +GenericName[zh_CN]=查询 +GenericName[zh_TW]=查詢 +Name=Queries +Name[bg]=Заявки +Name[ca]=Consultes +Name[cs]=Dotazy +Name[cy]=Ymholiadau +Name[da]=Forespørgsler +Name[de]=Abfragen +Name[el]=Ερωτήματα +Name[eo]=Serĉmendoj +Name[es]=Consultas +Name[et]=Päringud +Name[eu]=Kontsultak +Name[fa]=پرسوجوها +Name[fi]=Kyselyt +Name[fr]=Requêtes +Name[ga]=Iarratais +Name[gl]=Pesquisas +Name[he]=שאילתות +Name[hi]=क्वैरीज़ +Name[hr]=Upiti +Name[hu]=Lekérdezések +Name[is]=Fyrirspurnir +Name[it]=Interrogazioni +Name[ja]=クエリ +Name[km]=សំណួរ +Name[lt]=Užklausos +Name[lv]=Vaicājumi +Name[ms]=Pertanyaan +Name[nb]=Spørringer +Name[nds]=Affragen +Name[ne]=क्वेरीहरू +Name[nn]=Spørjingar +Name[pl]=Zapytania +Name[pt]=Procuras +Name[pt_BR]=Consultas +Name[ru]=Запросы +Name[se]=Jearahusat +Name[sk]=Otázky +Name[sl]=Poizvedbe +Name[sr]=Упити +Name[sr@Latn]=Upiti +Name[sv]=Förfrågningar +Name[ta]=கேள்விகள் +Name[tg]=Талаботҳо +Name[tr]=Sorgular +Name[uk]=Запити +Name[uz]=Talablar +Name[uz@cyrillic]=Талаблар +Name[zh_CN]=查询 +Name[zh_TW]=查詢 +X-KDE-Library=kexihandler_query +X-KDE-ParentApp=kexi +X-Kexi-PartVersion=2 +X-Kexi-TypeName=query +X-Kexi-TypeMime=kexi/query +X-Kexi-ItemIcon=query +X-Kexi-SupportsDataExport=true +X-Kexi-SupportsPrinting=true diff --git a/kexi/plugins/queries/kexiquerypart.cpp b/kexi/plugins/queries/kexiquerypart.cpp new file mode 100644 index 00000000..6cecfcf1 --- /dev/null +++ b/kexi/plugins/queries/kexiquerypart.cpp @@ -0,0 +1,310 @@ +/* This file is part of the KDE project + Copyright (C) 2004 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 "kexiquerypart.h" + +#include <kdebug.h> +#include <kgenericfactory.h> + +#include <keximainwindow.h> +#include <kexidialogbase.h> +#include <kexiproject.h> +#include <kexipartinfo.h> + +#include <kexidb/cursor.h> +#include <kexidb/parser/parser.h> + +#include "kexiqueryview.h" +#include "kexiquerydesignerguieditor.h" +#include "kexiquerydesignersql.h" + +//------------------------------------------------ + +KexiQueryPart::KexiQueryPart(QObject *parent, const char *name, const QStringList &l) + : KexiPart::Part(parent, name, l) +{ + // REGISTERED ID: + m_registeredPartID = (int)KexiPart::QueryObjectType; + + m_names["instanceName"] + = i18n("Translate this word using only lowercase alphanumeric characters (a..z, 0..9). " + "Use '_' character instead of spaces. First character should be a..z character. " + "If you cannot use latin characters in your language, use english word.", + "query"); + m_names["instanceCaption"] = i18n("Query"); + m_supportedViewModes = Kexi::DataViewMode | Kexi::DesignViewMode | Kexi::TextViewMode; +} + +KexiQueryPart::~KexiQueryPart() +{ +} + +KexiDialogTempData* +KexiQueryPart::createTempData(KexiDialogBase* dialog) +{ + KexiQueryPart::TempData *data = new KexiQueryPart::TempData(dialog, dialog->mainWin()->project()->dbConnection()); + data->listenerInfoString = dialog->part()->instanceCaption() + " \"" + dialog->partItem()->name() + "\""; + return data; +} + +KexiViewBase* +KexiQueryPart::createView(QWidget *parent, KexiDialogBase* dialog, KexiPart::Item &item, int viewMode, QMap<QString,QString>*) +{ + Q_UNUSED( item ); + + kdDebug() << "KexiQueryPart::createView()" << endl; + + if (viewMode == Kexi::DataViewMode) { + return new KexiQueryView(dialog->mainWin(), parent, "dataview"); + } + else if (viewMode == Kexi::DesignViewMode) { + KexiQueryDesignerGuiEditor* view = new KexiQueryDesignerGuiEditor( + dialog->mainWin(), parent, "guieditor"); + //needed for updating tables combo box: + KexiProject *prj = dialog->mainWin()->project(); + connect(prj, SIGNAL(newItemStored(KexiPart::Item&)), + view, SLOT(slotNewItemStored(KexiPart::Item&))); + connect(prj, SIGNAL(itemRemoved(const KexiPart::Item&)), + view, SLOT(slotItemRemoved(const KexiPart::Item&))); + connect(prj, SIGNAL(itemRenamed(const KexiPart::Item&, const QCString&)), + view, SLOT(slotItemRenamed(const KexiPart::Item&, const QCString&))); + +// connect(dialog->mainWin()->project(), SIGNAL(tableCreated(KexiDB::TableSchema&)), +// view, SLOT(slotTableCreated(KexiDB::TableSchema&))); + return view; + } + else if (viewMode == Kexi::TextViewMode) { + return new KexiQueryDesignerSQLView(dialog->mainWin(), parent, "sqldesigner"); + } + + return 0; +} + +bool +KexiQueryPart::remove(KexiMainWindow *win, KexiPart::Item &item) +{ + if (!win || !win->project() || !win->project()->dbConnection()) + return false; + KexiDB::Connection *conn = win->project()->dbConnection(); + KexiDB::QuerySchema *sch = conn->querySchema(item.identifier()); + if (sch) + return conn->dropQuery( sch ); + //last chance: just remove item + return conn->removeObject( item.identifier() ); +} + +#if 0 +KexiPart::DataSource * +KexiQueryPart::dataSource() +{ + return new KexiQueryDataSource(this); +} + +void KexiQueryPart::initPartActions( KActionCollection *col ) +{ +} + +void KexiQueryPart::initInstanceActions( int mode, KActionCollection *col ) +{ + if (mode==Kexi::DataViewMode) { + } + else if (mode==Kexi::DesignViewMode) { + } + else if (mode==Kexi::TextViewMode) { +// new KAction(i18n("Check Query"), "test_it", 0, this, SLOT(slotCheckQuery()), col, "querypart_check_query"); + +//TODO new KAction(i18n("Execute Query"), "?????", 0, this, SLOT(checkQuery()), col, "querypart_execute_query"); + } +} +#endif + +void KexiQueryPart::initPartActions() +{ +} + +void KexiQueryPart::initInstanceActions() +{ +// new KAction(i18n("Check Query"), "test_it", 0, this, SLOT(slotCheckQuery()), +// m_instanceGuiClients[Kexi::DesignViewMode]->actionCollection(), "querypart_check_query"); + + KAction *a = createSharedAction(Kexi::TextViewMode, i18n("Check Query"), "test_it", + Key_F9, "querypart_check_query"); + a->setToolTip(i18n("Check Query")); + a->setWhatsThis(i18n("Checks query for validity.")); + + a = createSharedToggleAction( + Kexi::TextViewMode, i18n("Show SQL History"), "view_top_bottom"/*TODO other icon*/, + 0, "querypart_view_toggle_history"); + a->setWhatsThis(i18n("Shows or hides SQL editor's history.")); + +// setActionAvailable("querypart_check_query", true); +} + +KexiDB::SchemaData* +KexiQueryPart::loadSchemaData(KexiDialogBase *dlg, const KexiDB::SchemaData& sdata, int viewMode) +{ + KexiQueryPart::TempData * temp = static_cast<KexiQueryPart::TempData*>(dlg->tempData()); + QString sqlText; + if (!loadDataBlock( dlg, sqlText, "sql" )) { + return 0; + } + KexiDB::Parser *parser = dlg->mainWin()->project()->sqlParser(); + parser->parse( sqlText ); + KexiDB::QuerySchema *query = parser->query(); + //error? + if (!query) { + if (viewMode==Kexi::TextViewMode) { + //for SQL view, no parsing is initially needed: + //-just make a copy: + return KexiPart::Part::loadSchemaData(dlg, sdata, viewMode); + } + /* Set this to true on data loading loadSchemaData() to indicate that TextView mode + could be used instead of DataView or DesignView, because there are problems + with opening object. */ + temp->proposeOpeningInTextViewModeBecauseOfProblems = true; + //todo + return 0; + } + query->debug(); + (KexiDB::SchemaData&)*query = sdata; //copy main attributes + + temp->registerTableSchemaChanges(query); + + query->debug(); + return query; +} + +QString KexiQueryPart::i18nMessage(const QCString& englishMessage, KexiDialogBase* dlg) const +{ + Q_UNUSED(dlg); + if (englishMessage=="Design of object \"%1\" has been modified.") + return i18n("Design of query \"%1\" has been modified."); + if (englishMessage=="Object \"%1\" already exists.") + return i18n("Query \"%1\" already exists."); + + return englishMessage; +} + +tristate KexiQueryPart::rename(KexiMainWindow *win, KexiPart::Item &item, const QString& newName) +{ + Q_UNUSED(newName); + if (!win->project()->dbConnection()) + return false; + win->project()->dbConnection()->setQuerySchemaObsolete( item.name() ); + return true; +} + +//---------------- + +KexiQueryPart::TempData::TempData(KexiDialogBase* parent, KexiDB::Connection *conn) + : KexiDialogTempData(parent) + , KexiDB::Connection::TableSchemaChangeListenerInterface() + , queryChangedInPreviousView(false) + , m_query(0) +{ + this->conn = conn; +} + +KexiQueryPart::TempData::~TempData() +{ + conn->unregisterForTablesSchemaChanges(*this); +} + +void KexiQueryPart::TempData::clearQuery() +{ + if (!m_query) + return; + unregisterForTablesSchemaChanges(); + m_query->clear(); +} + +void KexiQueryPart::TempData::unregisterForTablesSchemaChanges() +{ + conn->unregisterForTablesSchemaChanges(*this); +} + +void KexiQueryPart::TempData::registerTableSchemaChanges(KexiDB::QuerySchema *q) +{ + if (!q) + return; + for (KexiDB::TableSchema::ListIterator it(*q->tables()); + it.current(); ++it) + { + conn->registerForTableSchemaChanges(*this, *it.current()); + } +} + +tristate KexiQueryPart::TempData::closeListener() +{ + KexiDialogBase* dlg = static_cast<KexiDialogBase*>(parent()); + return dlg->mainWin()->closeDialog(dlg); +} + +KexiDB::QuerySchema *KexiQueryPart::TempData::takeQuery() +{ + KexiDB::QuerySchema *query = m_query; + m_query = 0; + return query; +} + +void KexiQueryPart::TempData::setQuery(KexiDB::QuerySchema *query) +{ + if (m_query && m_query == query) + return; + if (m_query + /* query not owned by dialog */ + && (static_cast<KexiDialogBase*>(parent())->schemaData() != static_cast<KexiDB::SchemaData*>( m_query ))) + { + delete m_query; + } + m_query = query; +} + +//---------------- + +#if 0 +KexiQueryDataSource::KexiQueryDataSource(KexiPart::Part *part) + : KexiPart::DataSource(part) +{ +} + +KexiQueryDataSource::~KexiQueryDataSource() +{ +} + +KexiDB::FieldList * +KexiQueryDataSource::fields(KexiProject *, const KexiPart::Item &) +{ + return 0; +} + +KexiDB::Cursor * +KexiQueryDataSource::cursor(KexiProject *, const KexiPart::Item &, bool) +{ + return 0; +} +#endif + +//---------------- + +K_EXPORT_COMPONENT_FACTORY( kexihandler_query, KGenericFactory<KexiQueryPart>("kexihandler_query") ) + +#include "kexiquerypart.moc" + diff --git a/kexi/plugins/queries/kexiquerypart.h b/kexi/plugins/queries/kexiquerypart.h new file mode 100644 index 00000000..6b16f28d --- /dev/null +++ b/kexi/plugins/queries/kexiquerypart.h @@ -0,0 +1,118 @@ +/* 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. +*/ + +#ifndef KEXIQUERYPART_H +#define KEXIQUERYPART_H + +#include <qmap.h> + +#include <kexidialogbase.h> +#include <kexipart.h> +#include <kexipartitem.h> +//#include <kexipartdatasource.h> + +#include <kexidb/queryschema.h> +#include <kexidb/connection.h> + +class KexiMainWin; +namespace KexiDB +{ + class QuerySchema; + class Connection; +} + +class KexiProject; + +//! @short Kexi Query Designer Plugin. +class KexiQueryPart : public KexiPart::Part +{ + Q_OBJECT + + public: + KexiQueryPart(QObject *parent, const char *name, const QStringList &); + virtual ~KexiQueryPart(); + + virtual bool remove(KexiMainWindow *win, KexiPart::Item &item); + + //! @short Temporary data kept in memory while switching between Query Dialog's views + class TempData : public KexiDialogTempData, + public KexiDB::Connection::TableSchemaChangeListenerInterface + { + public: + TempData(KexiDialogBase* parent, KexiDB::Connection *conn); + virtual ~TempData(); + virtual tristate closeListener(); + void clearQuery(); + void unregisterForTablesSchemaChanges(); + void registerTableSchemaChanges(KexiDB::QuerySchema *q); + + /*! Assigns query \a query for this data. + Existing query (available using query()) is deleted but only + if it is not owned by parent dialog (i.e. != KexiDialogBase::schemaData()). + \a query can be 0. + If \a query is equal to existing query, nothing is performed. + */ + void setQuery(KexiDB::QuerySchema *query); + + //! \return query associated with this data + KexiDB::QuerySchema *query() const { return m_query; } + + //! Takes query associated with this data (without deleting) and returns it. + //! After this call query() == 0 + KexiDB::QuerySchema *takeQuery(); + + //! Connection used for retrieving definition of the query + KexiDB::Connection *conn; + + /*! true, if \a query member has changed in previous view. + Used on view switching. We're checking this flag to see if we should + rebuild internal structure for DesignViewMode of regenerated sql text + in TextViewMode after switch from other view. */ + bool queryChangedInPreviousView : 1; + + protected: + KexiDB::QuerySchema *m_query; + }; + + virtual QString i18nMessage(const QCString& englishMessage, + KexiDialogBase* dlg) const; + + /*! Renames stored data pointed by \a item to \a newName. + Reimplemented to mark the query obsolete by using KexiDB::Connection::setQuerySchemaObsolete(). */ + virtual tristate rename(KexiMainWindow * win, KexiPart::Item & item, const QString& newName); + + protected: + virtual KexiDialogTempData* createTempData(KexiDialogBase* dialog); + + virtual KexiViewBase* createView(QWidget *parent, KexiDialogBase* dialog, + KexiPart::Item &item, int viewMode = Kexi::DataViewMode, QMap<QString,QString>* staticObjectArgs = 0); + +// virtual void initPartActions( KActionCollection *col ); +// virtual void initInstanceActions( int mode, KActionCollection *col ); + + virtual void initPartActions(); + virtual void initInstanceActions(); + + virtual KexiDB::SchemaData* loadSchemaData(KexiDialogBase *dlg, + const KexiDB::SchemaData& sdata, int viewMode); +}; + +#endif + diff --git a/kexi/plugins/queries/kexiquerypartinstui.rc b/kexi/plugins/queries/kexiquerypartinstui.rc new file mode 100644 index 00000000..405c4377 --- /dev/null +++ b/kexi/plugins/queries/kexiquerypartinstui.rc @@ -0,0 +1,24 @@ +<!DOCTYPE kpartgui> +<kpartgui name="kexiquerypartinst" version="4"> + +<MenuBar> + <Menu name="view" noMerge="1"> + <text>&View</text> + <Action name="querypart_view_toggle_history"/> + </Menu> + <Menu name="data"> + <text>&Data</text> + <Action name="querypart_check_query"/> + <Action name="querypart_execute_query"/> + <Merge/> + </Menu> +</MenuBar> + +<ToolBar name="designToolBar" fullWidth="false" noMerge="0"> + <text>Design</text> + <Action name="querypart_check_query"/> + <Action name="querypart_execute_query"/> + <Action name="querypart_view_toggle_history"/> +</ToolBar> + +</kpartgui> diff --git a/kexi/plugins/queries/kexiquerypartui.rc b/kexi/plugins/queries/kexiquerypartui.rc new file mode 100644 index 00000000..5b384aea --- /dev/null +++ b/kexi/plugins/queries/kexiquerypartui.rc @@ -0,0 +1,11 @@ +<!DOCTYPE kpartgui> +<kpartgui name="kexiquerypart" version="2"> + +<MenuBar> + <!-- (not needed - it is autogenerated!) <Menu name="widgets"> + <text>&Create</text> + <Action name="querypart_create"/> + </Menu --> +</MenuBar> + +</kpartgui> diff --git a/kexi/plugins/queries/kexiqueryview.cpp b/kexi/plugins/queries/kexiqueryview.cpp new file mode 100644 index 00000000..cf3fee96 --- /dev/null +++ b/kexi/plugins/queries/kexiqueryview.cpp @@ -0,0 +1,154 @@ +/* This file is part of the KDE project + Copyright (C) 2004 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 <kexiproject.h> +#include <kexidb/connection.h> +#include <kexidb/parser/parser.h> +#include <kexidb/cursor.h> +#include <keximainwindow.h> +#include <kexiutils/utils.h> + +#include "kexiqueryview.h" +#include "kexiquerydesignersql.h" +#include "kexiquerydesignerguieditor.h" +#include "kexiquerypart.h" +#include <widget/tableview/kexitableview.h> +#include <widget/kexiqueryparameters.h> + +//! @internal +class KexiQueryView::Private +{ + public: + Private() + : cursor(0) +// , queryHasBeenChangedInViewMode( Kexi::NoViewMode ) + {} + ~Private() {} + KexiDB::Cursor *cursor; + /*! Used in storeNewData(), storeData() to decide whether + we should ask other view to save changes. + Stores information about view mode. */ +// int queryHasBeenChangedInViewMode; +}; + +//--------------------------------------------------------------------------------- + +KexiQueryView::KexiQueryView(KexiMainWindow *win, QWidget *parent, const char *name) + : KexiDataTable(win, parent, name) + , d( new Private() ) +{ + tableView()->setInsertingEnabled(false); //default +} + +KexiQueryView::~KexiQueryView() +{ + if (d->cursor) + d->cursor->connection()->deleteCursor(d->cursor); + delete d; +} + +tristate KexiQueryView::executeQuery(KexiDB::QuerySchema *query) +{ + if (!query) + return false; + KexiUtils::WaitCursor wait; + KexiDB::Cursor *oldCursor = d->cursor; + KexiDB::debug( query->parameters() ); + bool ok; + QValueList<QVariant> params; + { + KexiUtils::WaitCursorRemover remover; + params = KexiQueryParameters::getParameters(this, + *mainWin()->project()->dbConnection()->driver(), *query, ok); + } + if (!ok) {//input cancelled + return cancelled; + } + d->cursor = mainWin()->project()->dbConnection()->executeQuery(*query, params); + if (!d->cursor) { + parentDialog()->setStatus(parentDialog()->mainWin()->project()->dbConnection(), + i18n("Query executing failed.")); + //todo: also provide server result and sql statement + return false; + } + setData(d->cursor); + +//! @todo remove close() when dynamic cursors arrive + d->cursor->close(); + + if (oldCursor) + oldCursor->connection()->deleteCursor(oldCursor); + +//! @todo maybe allow writing and inserting for single-table relations? + tableView()->setReadOnly( true ); +//! @todo maybe allow writing and inserting for single-table relations? + //set data model itself read-only too + tableView()->data()->setReadOnly( true ); + tableView()->setInsertingEnabled( false ); + return true; +} + +tristate KexiQueryView::afterSwitchFrom(int mode) +{ + if (mode==Kexi::NoViewMode) { + KexiDB::QuerySchema *querySchema = static_cast<KexiDB::QuerySchema *>(parentDialog()->schemaData()); + const tristate result = executeQuery(querySchema); + if (true != result) + return result; + } + else if (mode==Kexi::DesignViewMode || Kexi::TextViewMode) { + KexiQueryPart::TempData * temp = static_cast<KexiQueryPart::TempData*>(parentDialog()->tempData()); + + //remember what view we should use to store data changes, if needed +// if (temp->queryChangedInPreviousView) +// d->queryHasBeenChangedInViewMode = mode; +// else +// d->queryHasBeenChangedInViewMode = Kexi::NoViewMode; + + const tristate result = executeQuery(temp->query()); + if (true != result) + return result; + } + return true; +} + +KexiDB::SchemaData* KexiQueryView::storeNewData(const KexiDB::SchemaData& sdata, bool &cancel) +{ + KexiViewBase * view = parentDialog()->viewThatRecentlySetDirtyFlag(); + if (dynamic_cast<KexiQueryDesignerGuiEditor*>(view)) + return dynamic_cast<KexiQueryDesignerGuiEditor*>(view)->storeNewData(sdata, cancel); + if (dynamic_cast<KexiQueryDesignerSQLView*>(view)) + return dynamic_cast<KexiQueryDesignerSQLView*>(view)->storeNewData(sdata, cancel); + return 0; +} + +tristate KexiQueryView::storeData(bool dontAsk) +{ + KexiViewBase * view = parentDialog()->viewThatRecentlySetDirtyFlag(); + if (dynamic_cast<KexiQueryDesignerGuiEditor*>(view)) + return dynamic_cast<KexiQueryDesignerGuiEditor*>(view)->storeData(dontAsk); + if (dynamic_cast<KexiQueryDesignerSQLView*>(view)) + return dynamic_cast<KexiQueryDesignerSQLView*>(view)->storeData(dontAsk); + return false; +} + + +#include "kexiqueryview.moc" + diff --git a/kexi/plugins/queries/kexiqueryview.h b/kexi/plugins/queries/kexiqueryview.h new file mode 100644 index 00000000..f0083738 --- /dev/null +++ b/kexi/plugins/queries/kexiqueryview.h @@ -0,0 +1,58 @@ +/* This file is part of the KDE project + Copyright (C) 2004 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. +*/ + +#ifndef KEXIQUERYVIEW_H +#define KEXIQUERYVIEW_H + +#include <kexidatatable.h> + +namespace KexiDB +{ + class QuerySchema; +} +class KexiMainWindow; + +class KexiQueryView : public KexiDataTable +{ + Q_OBJECT + + public: + KexiQueryView(KexiMainWindow *win, QWidget *parent, const char *name=0); + ~KexiQueryView(); + + protected: + virtual tristate afterSwitchFrom(int mode); + + virtual KexiDB::SchemaData* storeNewData(const KexiDB::SchemaData& sdata, bool &cancel); + + virtual tristate storeData(bool dontAsk = false); + + /*! Executes query \a query, filling the table view with query results. + \return true on success, false on failure and cancelled when user has + cancelled execution (for example when she pressed the Cancel button + of the "Enter Query Parameter" input dialog. */ + tristate executeQuery(KexiDB::QuerySchema *query); + + class Private; + Private *d; +}; + +#endif + |