diff options
Diffstat (limited to 'kexi/plugins')
292 files changed, 50064 insertions, 0 deletions
diff --git a/kexi/plugins/Makefile.am b/kexi/plugins/Makefile.am new file mode 100644 index 00000000..6e0a7432 --- /dev/null +++ b/kexi/plugins/Makefile.am @@ -0,0 +1,15 @@ +#if compile_kexi_reports_plugin +# REPORTS=reports +#endif + +if compile_kexi_macros_plugin + MACRODIR=macros +endif + +if compile_kross + SCRIPTINGDIR=scripting +endif + +SUBDIRS = tables relations migration queries forms $(SCRIPTINGDIR) $(MACRODIR) importexport + +#$(REPORTS) importwizard relations diff --git a/kexi/plugins/Makefile.common b/kexi/plugins/Makefile.common new file mode 100644 index 00000000..d49a2a0f --- /dev/null +++ b/kexi/plugins/Makefile.common @@ -0,0 +1,2 @@ +INCLUDES += $(LIB_KEXI_KMDI_INCLUDES) + diff --git a/kexi/plugins/configure.in.in b/kexi/plugins/configure.in.in new file mode 100644 index 00000000..6d2cffcb --- /dev/null +++ b/kexi/plugins/configure.in.in @@ -0,0 +1,20 @@ +# disabled +#AC_ARG_ENABLE(kexi-reports, +# AC_HELP_STRING([--enable-kexi-reports], +# [build Kexi reports plugin (EXPERIMENTAL) [default=no]]), +# compile_kexi_reports_plugin=$enableval, compile_kexi_reports_plugin=no) +#AM_CONDITIONAL(compile_kexi_reports_plugin, test "x$compile_kexi_reports_plugin" != "xno") +# +#if test "$compile_kexi_reports_plugin" == "yes"; then +# AC_DEFINE(KEXI_REPORTS_SUPPORT, 1, [build Kexi reports plugin]) +#fi + +AC_ARG_ENABLE(kexi-macros, + AC_HELP_STRING([--enable-kexi-macros], + [build Kexi macro plugin (EXPERIMENTAL) [default=yes]]), + compile_kexi_macros_plugin=$enableval, compile_kexi_macros_plugin=no) +AM_CONDITIONAL(compile_kexi_macros_plugin, test "x$compile_kexi_macros_plugin" == "xyes") + +if test "$compile_kexi_macros_plugin" == "yes"; then + AC_DEFINE(KEXI_MACROS_SUPPORT, 1, [build Kexi macros plugin]) +fi diff --git a/kexi/plugins/configure.in.mid b/kexi/plugins/configure.in.mid new file mode 100644 index 00000000..c8ca24b1 --- /dev/null +++ b/kexi/plugins/configure.in.mid @@ -0,0 +1,26 @@ +if test -s $srcdir/inst-apps ; then + SUBDIRLIST=`cat $srcdir/inst-apps` +else + SUBDIRLIST=`cat $srcdir/subdirs` +fi + +# fallback (KDE_CREATE_SUBDIRLIST has this fallback, so I have put it here too.) +if test -z "$SUBDIRLIST" ; then + SUBDIRLIST=`ls -1 $srcdir` +fi + +# first check which main apllication we could compile +for args in $SUBDIRLIST ; do + case $args in + kugar) COMPILE_PLUGIN_KUGAR="$args " ;; + esac +done + +# now remove the applications the user has asked not to compile +for args in $DO_NOT_COMPILE ; do + case $args in + kugar) COMPILE_PLUGIN_KUGAR= ;; + esac +done + +AM_CONDITIONAL(compile_plugin_KUGAR, test -n "$COMPILE_PLUGIN_KUGAR") diff --git a/kexi/plugins/forms/Makefile.am b/kexi/plugins/forms/Makefile.am new file mode 100644 index 00000000..e01b4f6c --- /dev/null +++ b/kexi/plugins/forms/Makefile.am @@ -0,0 +1,56 @@ +include $(top_srcdir)/kexi/Makefile.global + +kde_module_LTLIBRARIES = kexihandler_form.la kformdesigner_kexidbwidgets.la + +kexihandler_form_la_SOURCES = kexiforms.cpp + +kexihandler_form_la_LDFLAGS = $(all_libraries) $(KDE_PLUGIN) $(VER_INFO) -module -no-undefined +kexihandler_form_la_LIBADD = $(top_builddir)/kexi/core/libkexicore.la \ + $(top_builddir)/kexi/widget/utils/libkexiguiutils.la \ + $(top_builddir)/kexi/widget/tableview/libkexidatatable.la \ + $(top_builddir)/kexi/widget/libkexiextendedwidgets.la \ + $(top_builddir)/kexi/formeditor/libkformdesigner.la \ + $(top_builddir)/lib/koproperty/libkoproperty.la \ + ./libkexiformutils.la + +kformdesigner_kexidbwidgets_la_LDFLAGS = $(all_libraries) $(KDE_PLUGIN) $(VER_INFO) -module -no-undefined +kformdesigner_kexidbwidgets_la_SOURCES = kexidbfactory.cpp +kformdesigner_kexidbwidgets_la_LIBADD = $(top_builddir)/kexi/formeditor/libkformdesigner.la \ + $(top_builddir)/kexi/widget/libkexiextendedwidgets.la \ + ./libkexiformutils.la + +lib_LTLIBRARIES = libkexiformutils.la +libkexiformutils_la_SOURCES = kexiformdataiteminterface.cpp kexidataawarewidgetinfo.cpp \ + kexidataprovider.cpp kexiformscrollview.cpp kexiformeventhandler.cpp \ + kexidbtextwidgetinterface.cpp kexiactionselectiondialog.cpp kexiformmanager.cpp \ + kexidatasourcepage.cpp kexiformpart.cpp kexiformview.cpp +libkexiformutils_la_LDFLAGS = $(all_libraries) $(VER_INFO) -no-undefined +libkexiformutils_la_LIBADD = $(top_builddir)/kexi/core/libkexicore.la \ + $(top_builddir)/kexi/widget/libkexiextendedwidgets.la \ + $(top_builddir)/kexi/formeditor/libkformdesigner.la \ + $(top_builddir)/kexi/plugins/forms/widgets/libkexiformutilswidgets.la + +kformdesignerservicesdir=$(kde_servicesdir)/kformdesigner +kformdesignerservices_DATA=kformdesigner_kexidbfactory.desktop + +servicesdir=$(kde_servicesdir)/kexi +services_DATA=kexiformhandler.desktop + +rcdir = $(kde_datadir)/kexi +rc_DATA = kexiformpartui.rc kexiformpartinstui.rc + +SUBDIRS = widgets . + +INCLUDES= -I$(top_srcdir)/kexi/core -I$(top_srcdir)/kexi \ + -I$(top_srcdir)/kexi/widget/utils \ + -I$(top_srcdir)/kexi/widget \ + -I$(top_srcdir)/kexi/formeditor \ + -I$(top_srcdir)/lib -I$(top_srcdir)/lib/koproperty -I$(top_srcdir)/lib/kofficecore \ + -I$(top_srcdir)/kexi/widget/tableview/private \ + -I$(top_srcdir)/kexi/widget/tableview $(all_includes) + +METASOURCES = AUTO + +include ../Makefile.common +noinst_HEADERS = kexidataprovider.h kexidbfactory.h \ + kexiformpart.h kexiformscrollview.h kexiformview.h
\ No newline at end of file diff --git a/kexi/plugins/forms/kexiactionselectiondialog.cpp b/kexi/plugins/forms/kexiactionselectiondialog.cpp new file mode 100644 index 00000000..26b4a9a6 --- /dev/null +++ b/kexi/plugins/forms/kexiactionselectiondialog.cpp @@ -0,0 +1,724 @@ +/* This file is part of the KDE project + Copyright (C) 2005-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 "kexiactionselectiondialog.h" +#include "kexiactionselectiondialog_p.h" + +#include <keximainwindow.h> +#include <kexipartitem.h> +#include <kexiproject.h> +#include <kexipartinfo.h> +#include <kexipart.h> +#include <kexiactioncategories.h> + +#include <klistview.h> +#include <kaction.h> +#include <kiconloader.h> +#include <kdebug.h> +#include <kstdguiitem.h> +#include <kpushbutton.h> + +#include <qbitmap.h> +#include <qlabel.h> +#include <qheader.h> +#include <qvbox.h> +#include <qtooltip.h> +#include <qwidgetstack.h> + +#include <widget/utils/klistviewitemtemplate.h> +#include <widget/kexibrowser.h> +#include <widget/kexibrowseritem.h> +#include <kexiutils/utils.h> + +typedef KListViewItemTemplate<QString> ActionSelectorDialogListItemBase; + +class ActionSelectorDialogListItem : public ActionSelectorDialogListItemBase +{ +public: + ActionSelectorDialogListItem(const QString& data, QListView *parent, QString label1) + : ActionSelectorDialogListItemBase(data, parent, label1) + , fifoSorting(true) + { + m_sortKey.sprintf("%2.2d", parent->childCount()); + } + + ActionSelectorDialogListItem(const QString& data, QListViewItem *parent, QString label1) + : ActionSelectorDialogListItemBase(data, parent, label1) + , fifoSorting(true) + { + m_sortKey.sprintf("%2.2d", parent->childCount()); + } + + virtual QString key( int column, bool ascending ) const + { + return fifoSorting ? m_sortKey : ActionSelectorDialogListItemBase::key(column, ascending); + } + + bool fifoSorting : 1; + +protected: + QString m_sortKey; +}; + +//--------------------------------------- + +ActionsListViewBase::ActionsListViewBase(QWidget* parent) + : KListView(parent) +{ + setResizeMode(QListView::AllColumns); + addColumn(""); + header()->hide(); + setColumnWidthMode(0, QListView::Maximum); + setAllColumnsShowFocus(true); + setTooltipColumn(0); +} + +ActionsListViewBase::~ActionsListViewBase() +{ +} + +QListViewItem *ActionsListViewBase::itemForAction(const QString& actionName) +{ + for (QListViewItemIterator it(this); it.current(); ++it) { + ActionSelectorDialogListItem* item = dynamic_cast<ActionSelectorDialogListItem*>(it.current()); + if (item && item->data == actionName) + return item; + } + return 0; +} + +void ActionsListViewBase::selectAction(const QString& actionName) +{ + QListViewItem *item = itemForAction(actionName); + if (item) { + setSelected(item, true); + ensureItemVisible(firstChild()); + ensureItemVisible(selectedItem()); + } +} + +//--------------------------------------- + +KActionsListViewBase::KActionsListViewBase(QWidget* parent, KexiMainWindow* mainWin) + : ActionsListViewBase(parent) + , m_mainWin(mainWin) +{ +} + +KActionsListViewBase::~KActionsListViewBase() {} + +void KActionsListViewBase::init() +{ + setSorting(0); + const QPixmap noIcon( KexiUtils::emptyIcon(KIcon::Small) ); + KActionPtrList sharedActions( m_mainWin->allActions() ); + const Kexi::ActionCategories *acat = Kexi::actionCategories(); + foreach (KActionPtrList::ConstIterator, it, sharedActions) { +// kdDebug() << (*it)->name() << " " << (*it)->text() << endl; + //! @todo group actions + //! @todo: store KAction* here? + const int actionCategories = acat->actionCategories((*it)->name()); + if (actionCategories==-1) { + kexipluginswarn << "KActionsListViewBase(): no category declared for action \"" + << (*it)->name() << "\"! Fix this!" << endl; + continue; + } + if (!isActionVisible((*it)->name(), actionCategories)) + continue; + ActionSelectorDialogListItem *pitem = new ActionSelectorDialogListItem((*it)->name(), + this, (*it)->toolTip().isEmpty() ? (*it)->text().replace("&", "") : (*it)->toolTip() ); + pitem->fifoSorting = false; //alpha sort + pitem->setPixmap( 0, (*it)->iconSet( KIcon::Small, 16 ).pixmap( QIconSet::Small, QIconSet::Active ) ); + if (!pitem->pixmap(0) || pitem->pixmap(0)->isNull()) + pitem->setPixmap( 0, noIcon ); + } +} + +//--------------------------------------- + +//! @internal Used to display KActions (in column 2) +class KActionsListView : public KActionsListViewBase +{ +public: + KActionsListView(QWidget* parent, KexiMainWindow* mainWin) + : KActionsListViewBase(parent, mainWin) + { + } + virtual ~KActionsListView() {} + + virtual bool isActionVisible(const char* actionName, int actionCategories) const { + Q_UNUSED(actionName); + return actionCategories & Kexi::GlobalActionCategory; + } +}; + +//! @internal Used to display KActions (in column 2) +class CurrentFormActionsListView : public KActionsListViewBase +{ +public: + CurrentFormActionsListView(QWidget* parent, KexiMainWindow* mainWin) + : KActionsListViewBase(parent, mainWin) + { + } + virtual ~CurrentFormActionsListView() {} + + virtual bool isActionVisible(const char* actionName, int actionCategories) const { + return actionCategories & Kexi::WindowActionCategory + && Kexi::actionCategories()->actionSupportsObjectType(actionName, KexiPart::FormObjectType); + } +}; + +//! @internal a list view displaying action categories user can select from (column 1) +class ActionCategoriesListView : public ActionsListViewBase +{ +public: + ActionCategoriesListView(QWidget* parent) //, KexiProject& project) + : ActionsListViewBase(parent) + { + QListViewItem *item = new ActionSelectorDialogListItem("noaction", this, i18n("No action") ); + const QPixmap noIcon( KexiUtils::emptyIcon(KIcon::Small) ); + item->setPixmap(0, noIcon); + item = new ActionSelectorDialogListItem("kaction", this, i18n("Application actions") ); + item->setPixmap(0, SmallIcon("form_action")); + + KexiPart::PartInfoList *pl = Kexi::partManager().partInfoList(); + for (KexiPart::Info *info = pl->first(); info; info = pl->next()) { + KexiPart::Part *part = Kexi::partManager().part(info); + if (!info->isVisibleInNavigator() || !part) + continue; + item = new KexiBrowserItem(this, info); + item->setText(0, part->instanceCaption()); + } + QListViewItem *formItem = itemForAction("form"); + if (formItem) { + item = new ActionSelectorDialogListItem("currentForm", formItem, + i18n("Current form's actions", "Current")); + } + adjustColumn(0); + setMinimumWidth( columnWidth(0) + 6 ); + } + + ~ActionCategoriesListView() + { + } + + //! \return item for action \a actionName, reimplemented to support KexiBrowserItem items + virtual QListViewItem *itemForAction(const QString& actionName) + { + for (QListViewItemIterator it(this); it.current(); ++it) { + //simple case + ActionSelectorDialogListItem* item = dynamic_cast<ActionSelectorDialogListItem*>(it.current()); + if (item) { + if (item->data == actionName) + return it.current(); + continue; + } + KexiBrowserItem* bitem = dynamic_cast<KexiBrowserItem*>(it.current()); + if (bitem) { + if (bitem->info()->objectName() == actionName) + return it.current(); + } + } + return 0; + } +}; + +//! @internal Used to display list of actions available to executing (column 3) +class ActionToExecuteListView : public ActionsListViewBase +{ + public: + ActionToExecuteListView(QWidget* parent) + : ActionsListViewBase(parent) + { + } + + ~ActionToExecuteListView() + { + } + + //! Updates actions + void showActionsForMimeType(const QString& mimeType) { + if (m_currentMimeType == mimeType) + return; + m_currentMimeType = mimeType; + clear(); + KexiPart::Part *part = Kexi::partManager().partForMimeType( m_currentMimeType ); + if (!part) + return; + int supportedViewModes = part->supportedViewModes(); + ActionSelectorDialogListItem *item; + const QPixmap noIcon( KexiUtils::emptyIcon(KIcon::Small) ); + if (supportedViewModes & Kexi::DataViewMode) { + item = new ActionSelectorDialogListItem("open", this, i18n("Open in Data View")); + item->setPixmap(0, SmallIcon("fileopen")); + } + if (part->info()->isExecuteSupported()) { + item = new ActionSelectorDialogListItem("execute", this, i18n("Execute")); + item->setPixmap(0, SmallIcon("player_play")); + } + if (part->info()->isPrintingSupported()) { + ActionSelectorDialogListItem *printItem = new ActionSelectorDialogListItem( + "print", this, i18n("Print")); + printItem->setPixmap(0, SmallIcon("fileprint")); + KAction *a = KStdAction::printPreview(0, 0, 0); + item = new ActionSelectorDialogListItem("printPreview", printItem, + a->text().replace("&", "").replace("...", "")); + item->setPixmap(0, SmallIcon(a->icon())); + delete a; + item = new ActionSelectorDialogListItem("pageSetup", printItem, i18n("Show Page Setup")); + item->setPixmap(0, noIcon); + setOpen(printItem, true); + printItem->setExpandable(false); + } + if (part->info()->isDataExportSupported()) { + ActionSelectorDialogListItem *exportItem = new ActionSelectorDialogListItem( + "exportToCSV", this, + i18n("Note: use multiple rows if needed", "Export to File\nAs Data Table")); + exportItem->setMultiLinesEnabled(true); + exportItem->setPixmap(0, SmallIcon("table")); + item = new ActionSelectorDialogListItem("copyToClipboardAsCSV", + exportItem, + i18n("Note: use multiple rows if needed", "Copy to Clipboard\nAs Data Table")); + item->setPixmap(0, SmallIcon("table")); + item->setMultiLinesEnabled(true); + setOpen(exportItem, true); + exportItem->setExpandable(false); + } + item = new ActionSelectorDialogListItem("new", this, i18n("Create New Object")); + item->setPixmap(0, SmallIcon("filenew")); + if (supportedViewModes & Kexi::DesignViewMode) { + item = new ActionSelectorDialogListItem("design", this, i18n("Open in Design View")); + item->setPixmap(0, SmallIcon("edit")); + } + if (supportedViewModes & Kexi::TextViewMode) { + item = new ActionSelectorDialogListItem("editText", this, i18n("Open in Text View")); + item->setPixmap(0, noIcon); + } + item = new ActionSelectorDialogListItem("close", this, i18n("Close View")); + item->setPixmap(0, SmallIcon("fileclose")); + updateWidth(); + } + + void updateWidth() + { + adjustColumn(0); + setMinimumWidth( columnWidth(0) ); + } + + QString m_currentMimeType; +}; + +//------------------------------------- + +//! @internal +class KexiActionSelectionDialog::KexiActionSelectionDialogPrivate +{ +public: + KexiActionSelectionDialogPrivate() + : kactionPageWidget(0), kactionListView(0), objectsListView(0) + , currentFormActionsPageWidget(0) + , currentFormActionsListView(0) + , secondAnd3rdColumnMainWidget(0) + , hideActionToExecuteListView(false) + { + } + + void raiseWidget(QWidget *w) + { + secondAnd3rdColumnStack->raiseWidget( w ); + selectActionToBeExecutedLbl->setBuddy(w); + } + + void updateSelectActionToBeExecutedMessage(const QString& actionType) + { + QString msg; + if (actionType=="noaction") + msg = QString::null; + // hardcoded, but it's not that bad + else if (actionType=="macro") + msg = i18n("&Select macro to be executed after clicking \"%1\" button:").arg(actionWidgetName); + else if (actionType=="script") + msg = i18n("&Select script to be executed after clicking \"%1\" button:").arg(actionWidgetName); + //default: table/query/form/report... + else + msg = i18n("&Select object to be opened after clicking \"%1\" button:").arg(actionWidgetName); + selectActionToBeExecutedLbl->setText(msg); + } + + // changes 3rd column visibility + void setActionToExecuteSectionVisible(bool visible, bool force = false) + { + if (!force && hideActionToExecuteListView != visible) + return; + hideActionToExecuteListView = !visible; + actionToExecuteListView->hide(); + actionToExecuteLbl->hide(); + actionToExecuteListView->show(); + actionToExecuteLbl->show(); + } + + KexiMainWindow* mainWin; + QString actionWidgetName; + ActionCategoriesListView* actionCategoriesListView; //!< for column #1 + QWidget *kactionPageWidget; + KActionsListView* kactionListView; //!< for column #2 + KexiBrowser* objectsListView; //!< for column #2 + QWidget *currentFormActionsPageWidget; //!< for column #2 + CurrentFormActionsListView* currentFormActionsListView; //!< for column #2 + QWidget *emptyWidget; + QLabel* selectActionToBeExecutedLbl; + ActionToExecuteListView* actionToExecuteListView; + QLabel *actionToExecuteLbl; + QWidget *secondAnd3rdColumnMainWidget; + QGridLayout *glyr; + QGridLayout *secondAnd3rdColumnGrLyr; + QWidgetStack *secondAnd3rdColumnStack, *secondColumnStack; + bool hideActionToExecuteListView; +}; + +//------------------------------------- + +KexiActionSelectionDialog::KexiActionSelectionDialog(KexiMainWindow* mainWin, QWidget *parent, + const KexiFormEventAction::ActionData& action, const QCString& actionWidgetName) + : KDialogBase(parent, "actionSelectorDialog", true, i18n("Assigning Action to Command Button"), + KDialogBase::Ok | KDialogBase::Cancel ) + , d( new KexiActionSelectionDialogPrivate() ) +{ + d->mainWin = mainWin; + d->actionWidgetName = actionWidgetName; + setButtonOK( KGuiItem(i18n("Assign action", "&Assign"), "button_ok", i18n("Assign action")) ); + + QWidget *mainWidget = new QWidget( this ); + mainWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + setMainWidget(mainWidget); + +/* lbl 1 + +------------+ +-------------------------------+ + | | | [a] | + | 1st column | | +----------- + +------------+ | + | | | | 2nd column | | 3rd column | | + | | | + + + + | + | | | +------------+ +------------+ | + +------------+ +-------------------------------+ + \______________________________________________/ + glyr + [a]- QWidgetStack *secondAnd3rdColumnStack, + - for displaying KActions, the stack contains d->kactionPageWidget QWidget + - for displaying objects, the stack contains secondAnd3rdColumnMainWidget QWidget and QGridLayout *secondAnd3rdColumnGrLyr + - kactionPageWidget contains only a QVBoxLayout and label+kactionListView +*/ + d->glyr = new QGridLayout(mainWidget, 2, 2, KDialog::marginHint(), KDialog::spacingHint()); + d->glyr->setRowStretch(1, 1); + + // 1st column: action types + d->actionCategoriesListView = new ActionCategoriesListView(mainWidget); + d->actionCategoriesListView->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum); + d->glyr->addWidget(d->actionCategoriesListView, 1, 0); + connect( d->actionCategoriesListView, SIGNAL(selectionChanged(QListViewItem*)), + this, SLOT(slotActionCategorySelected(QListViewItem*))); + + QLabel *lbl = new QLabel(d->actionCategoriesListView, i18n("Action category:"), mainWidget); + lbl->setMinimumHeight(lbl->fontMetrics().height()*2); + lbl->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + lbl->setAlignment(Qt::AlignTop|Qt::AlignLeft|Qt::WordBreak); + d->glyr->addWidget(lbl, 0, 0, Qt::AlignTop|Qt::AlignLeft); + + // widget stack for 2nd and 3rd column + d->secondAnd3rdColumnStack = new QWidgetStack(mainWidget); + d->secondAnd3rdColumnStack->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + d->glyr->addMultiCellWidget(d->secondAnd3rdColumnStack, 0, 1, 1, 1);//, Qt::AlignTop|Qt::AlignLeft); + + d->secondAnd3rdColumnMainWidget = new QWidget(d->secondAnd3rdColumnStack); + d->secondAnd3rdColumnMainWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + d->secondAnd3rdColumnGrLyr = new QGridLayout(d->secondAnd3rdColumnMainWidget, 2, 2, 0, KDialog::spacingHint()); + d->secondAnd3rdColumnGrLyr->setRowStretch(1, 2); + d->secondAnd3rdColumnStack->addWidget(d->secondAnd3rdColumnMainWidget); + + // 2nd column: list of actions/objects + d->objectsListView = new KexiBrowser(d->secondAnd3rdColumnMainWidget, d->mainWin, 0/*features*/); + d->secondAnd3rdColumnGrLyr->addWidget(d->objectsListView, 1, 0); + connect(d->objectsListView, SIGNAL(selectionChanged(KexiPart::Item*)), + this, SLOT(slotItemForOpeningOrExecutingSelected(KexiPart::Item*))); + + d->selectActionToBeExecutedLbl = new QLabel(d->secondAnd3rdColumnMainWidget); + d->selectActionToBeExecutedLbl->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); + d->selectActionToBeExecutedLbl->setAlignment(Qt::AlignTop|Qt::AlignLeft|Qt::WordBreak); + d->selectActionToBeExecutedLbl->setMinimumHeight(d->selectActionToBeExecutedLbl->fontMetrics().height()*2); + d->secondAnd3rdColumnGrLyr->addWidget(d->selectActionToBeExecutedLbl, 0, 0, Qt::AlignTop|Qt::AlignLeft); + + d->emptyWidget = new QWidget(d->secondAnd3rdColumnStack); + d->secondAnd3rdColumnStack->addWidget(d->emptyWidget); + + // 3rd column: actions to execute + d->actionToExecuteListView = new ActionToExecuteListView(d->secondAnd3rdColumnMainWidget); + d->actionToExecuteListView->installEventFilter(this); //to be able to disable painting + d->actionToExecuteListView->viewport()->installEventFilter(this); //to be able to disable painting + d->actionToExecuteListView->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum); + connect(d->actionToExecuteListView, SIGNAL(executed(QListViewItem*)), + this, SLOT(slotActionToExecuteItemExecuted(QListViewItem*))); + connect(d->actionToExecuteListView, SIGNAL(selectionChanged(QListViewItem*)), + this, SLOT(slotActionToExecuteItemSelected(QListViewItem*))); + d->secondAnd3rdColumnGrLyr->addWidget(d->actionToExecuteListView, 1, 1); + + d->actionToExecuteLbl = new QLabel(d->actionToExecuteListView, + i18n("Action to execute:"), d->secondAnd3rdColumnMainWidget); + d->actionToExecuteLbl->installEventFilter(this); //to be able to disable painting + d->actionToExecuteLbl->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); + d->actionToExecuteLbl->setAlignment(Qt::AlignTop|Qt::AlignLeft|Qt::WordBreak); + d->secondAnd3rdColumnGrLyr->addWidget(d->actionToExecuteLbl, 0, 1, Qt::AlignTop|Qt::AlignLeft); + + // temporary show all sections to avoid resizing the dialog in the future + d->actionCategoriesListView->selectAction("table"); + d->setActionToExecuteSectionVisible(true); + adjustSize(); + resize(QMAX(700, width()), QMAX(450, height())); + d->actionToExecuteListView->updateWidth(); + + bool ok; + QString actionType, actionArg; + KexiPart::Info* partInfo = action.decodeString(actionType, actionArg, ok); + if (ok) { + d->actionCategoriesListView->selectAction(actionType); + if (actionType=="kaction") { + d->kactionListView->selectAction(actionArg); + d->kactionListView->setFocus(); + } + else if (actionType=="currentForm") { + d->currentFormActionsListView->selectAction(actionArg); + d->currentFormActionsListView->setFocus(); + } + else if (partInfo + && Kexi::partManager().part(partInfo)) // We use the Part Manager + // to determine whether the Kexi-plugin is installed and whether we like to show + // it in our list of actions. + { + KexiPart::Item *item = d->mainWin->project()->item(partInfo, actionArg); + if (d->objectsListView && item) { + d->objectsListView->selectItem(*item); + QString actionOption( action.option ); + if (actionOption.isEmpty()) + actionOption = "open"; // for backward compatibility + d->actionToExecuteListView->selectAction(actionOption); + d->objectsListView->setFocus(); + } + } + } + else {//invalid assignment or 'noaction' + d->actionCategoriesListView->selectAction("noaction"); + d->actionCategoriesListView->setFocus(); + } +} + +KexiActionSelectionDialog::~KexiActionSelectionDialog() +{ + delete d; +} + +void KexiActionSelectionDialog::slotKActionItemExecuted(QListViewItem*) +{ + accept(); +} + +void KexiActionSelectionDialog::slotKActionItemSelected(QListViewItem*) +{ + d->setActionToExecuteSectionVisible(false); + updateOKButtonStatus(); +} + +void KexiActionSelectionDialog::slotCurrentFormActionItemExecuted(QListViewItem*) +{ + accept(); +} + +void KexiActionSelectionDialog::slotCurrentFormActionItemSelected(QListViewItem*) +{ + d->setActionToExecuteSectionVisible(false); + updateOKButtonStatus(); +} + +void KexiActionSelectionDialog::slotItemForOpeningOrExecutingSelected(KexiPart::Item* item) +{ + d->setActionToExecuteSectionVisible(item); +} + +void KexiActionSelectionDialog::slotActionToExecuteItemExecuted(QListViewItem* item) +{ + if (!item) + return; + ActionSelectorDialogListItemBase *listItem = dynamic_cast<ActionSelectorDialogListItemBase*>(item); + if (listItem && !listItem->data.isEmpty()) + accept(); +} + +void KexiActionSelectionDialog::slotActionToExecuteItemSelected(QListViewItem*) +{ + updateOKButtonStatus(); +} + +void KexiActionSelectionDialog::slotActionCategorySelected(QListViewItem* item) +{ + ActionSelectorDialogListItem *simpleItem = dynamic_cast<ActionSelectorDialogListItem*>(item); + // simple case: part-less item, e.g. kaction: + if (simpleItem) { + d->updateSelectActionToBeExecutedMessage(simpleItem->data); + QString selectActionToBeExecutedMsg( + i18n("&Select action to be executed after clicking \"%1\" button:")); // msg for a label + if (simpleItem->data == "kaction") { + if (!d->kactionPageWidget) { + //create lbl+list view with a vlayout + d->kactionPageWidget = new QWidget(); + d->kactionPageWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + QVBoxLayout *vlyr = new QVBoxLayout(d->kactionPageWidget, 0, KDialog::spacingHint()); + d->kactionListView = new KActionsListView(d->kactionPageWidget, d->mainWin); + d->kactionListView->init(); + QLabel *lbl = new QLabel(d->kactionListView, selectActionToBeExecutedMsg.arg(d->actionWidgetName), + d->kactionPageWidget); + lbl->setAlignment(Qt::AlignTop|Qt::AlignLeft|Qt::WordBreak); + lbl->setMinimumHeight(lbl->fontMetrics().height()*2); + vlyr->addWidget(lbl); + vlyr->addWidget(d->kactionListView); + d->secondAnd3rdColumnStack->addWidget(d->kactionPageWidget); + connect(d->kactionListView, SIGNAL(executed(QListViewItem*)), + this, SLOT(slotKActionItemExecuted(QListViewItem*))); + connect( d->kactionListView, SIGNAL(selectionChanged(QListViewItem*)), + this, SLOT(slotKActionItemSelected(QListViewItem*))); + } + d->setActionToExecuteSectionVisible(false); + d->raiseWidget(d->kactionPageWidget); + slotKActionItemSelected(d->kactionListView->selectedItem()); //to refresh column #3 + } + else if (simpleItem->data == "currentForm") { + if (!d->currentFormActionsPageWidget) { + //create lbl+list view with a vlayout + d->currentFormActionsPageWidget = new QWidget(); + d->currentFormActionsPageWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + QVBoxLayout *vlyr = new QVBoxLayout(d->currentFormActionsPageWidget, 0, KDialog::spacingHint()); + d->currentFormActionsListView = new CurrentFormActionsListView( + d->currentFormActionsPageWidget, d->mainWin); + d->currentFormActionsListView->init(); + QLabel *lbl = new QLabel(d->currentFormActionsListView, + selectActionToBeExecutedMsg.arg(d->actionWidgetName), d->currentFormActionsPageWidget); + lbl->setAlignment(Qt::AlignTop|Qt::AlignLeft|Qt::WordBreak); + lbl->setMinimumHeight(lbl->fontMetrics().height()*2); + vlyr->addWidget(lbl); + vlyr->addWidget(d->currentFormActionsListView); + d->secondAnd3rdColumnStack->addWidget(d->currentFormActionsPageWidget); + connect(d->currentFormActionsListView, SIGNAL(executed(QListViewItem*)), + this, SLOT(slotCurrentFormActionItemExecuted(QListViewItem*))); + connect( d->currentFormActionsListView, SIGNAL(selectionChanged(QListViewItem*)), + this, SLOT(slotCurrentFormActionItemSelected(QListViewItem*))); + } + d->setActionToExecuteSectionVisible(false); + d->raiseWidget(d->currentFormActionsPageWidget); + slotCurrentFormActionItemSelected(d->currentFormActionsListView->selectedItem()); //to refresh column #3 + } + else if (simpleItem->data == "noaction") { + d->raiseWidget(d->emptyWidget); + d->objectsListView->clearSelection(); + //hide column #3 + d->setActionToExecuteSectionVisible(false); + } + d->actionCategoriesListView->update(); + updateOKButtonStatus(); + return; + } + // other case + KexiBrowserItem* browserItem = dynamic_cast<KexiBrowserItem*>(item); + if (browserItem) { + d->updateSelectActionToBeExecutedMessage(browserItem->info()->objectName()); + if (d->objectsListView->itemsMimeType().latin1()!=browserItem->info()->mimeType()) { + d->objectsListView->setProject(d->mainWin->project(), browserItem->info()->mimeType()); + d->actionToExecuteListView->showActionsForMimeType( browserItem->info()->mimeType() ); + d->setActionToExecuteSectionVisible(false); + } + if (d->secondAnd3rdColumnStack->visibleWidget()!=d->secondAnd3rdColumnMainWidget) { + d->raiseWidget( d->secondAnd3rdColumnMainWidget ); + d->objectsListView->clearSelection(); + d->setActionToExecuteSectionVisible(false, true); + } + else + d->raiseWidget( d->secondAnd3rdColumnMainWidget ); + } + d->actionCategoriesListView->update(); + updateOKButtonStatus(); +} + +KexiMainWindow* KexiActionSelectionDialog::mainWin() const +{ + return d->mainWin; +} + +KexiFormEventAction::ActionData KexiActionSelectionDialog::currentAction() const +{ + KexiFormEventAction::ActionData data; + ActionSelectorDialogListItem *simpleItem = dynamic_cast<ActionSelectorDialogListItem*>( + d->actionCategoriesListView->selectedItem()); + // simple case: part-less item, e.g. kaction: + if (simpleItem) { + if (simpleItem->data == "kaction") { + if (d->kactionListView->selectedItem()) { + data.string = QString("kaction:") + + dynamic_cast<ActionSelectorDialogListItem*>( d->kactionListView->selectedItem() )->data; + return data; + } + } + else if (simpleItem->data == "currentForm") { + if (d->currentFormActionsListView->selectedItem()) { + data.string = QString("currentForm:") + + dynamic_cast<ActionSelectorDialogListItem*>( + d->currentFormActionsListView->selectedItem() )->data; + return data; + } + } + } + KexiBrowserItem* browserItem = dynamic_cast<KexiBrowserItem*>( d->actionCategoriesListView->selectedItem() ); + if (browserItem) { + ActionSelectorDialogListItem *actionToExecute = dynamic_cast<ActionSelectorDialogListItem*>( + d->actionToExecuteListView->selectedItem()); + if (d->objectsListView && actionToExecute && !actionToExecute->data.isEmpty()) { + KexiPart::Item* partItem = d->objectsListView->selectedPartItem(); + KexiPart::Info* partInfo = partItem ? Kexi::partManager().infoForMimeType( partItem->mimeType() ) : 0; + if (partInfo) { + // opening or executing: table:name, query:name, form:name, macro:name, script:name, etc. + data.string = QString("%1:%2").arg(partInfo->objectName()).arg(partItem->name()); + data.option = actionToExecute->data; + return data; + } + } + } + return data; // No Action +} + +void KexiActionSelectionDialog::updateOKButtonStatus() +{ + QPushButton *btn = actionButton(Ok); + ActionSelectorDialogListItem *simpleItem = dynamic_cast<ActionSelectorDialogListItem*>( + d->actionCategoriesListView->selectedItem()); + btn->setEnabled( (simpleItem && simpleItem->data == "noaction") || !currentAction().isEmpty() ); +} + +bool KexiActionSelectionDialog::eventFilter(QObject *o, QEvent *e) +{ + if (d->hideActionToExecuteListView) + return true; + return KDialogBase::eventFilter(o, e); +} + +#include "kexiactionselectiondialog.moc" +#include "kexiactionselectiondialog_p.moc" diff --git a/kexi/plugins/forms/kexiactionselectiondialog.h b/kexi/plugins/forms/kexiactionselectiondialog.h new file mode 100644 index 00000000..6b6a896b --- /dev/null +++ b/kexi/plugins/forms/kexiactionselectiondialog.h @@ -0,0 +1,71 @@ +/* This file is part of the KDE project + Copyright (C) 2005-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 KEXIACTIONSELECTIONDIALOG_H +#define KEXIACTIONSELECTIONDIALOG_H + +#include <kdialogbase.h> +#include "kexiformeventhandler.h" + +class KexiMainWindow; +class KListView; +namespace KexiPart { + class Item; +} + +//! @short A dialog for selecting an action to be executed for a form's command button +/*! Available actions are: + - application's global actions like "edit->copy" (KAction-based) + - opening/printing/executing of selected object (table/query/form/script/macrto, etc.) +*/ +class KEXIFORMUTILS_EXPORT KexiActionSelectionDialog : public KDialogBase +{ + Q_OBJECT + public: + KexiActionSelectionDialog(KexiMainWindow* mainWin, QWidget *parent, + const KexiFormEventAction::ActionData& action, const QCString& actionWidgetName); + ~KexiActionSelectionDialog(); + + /*! \return selected action data or empty action if dialog has been rejected + or "No action" has been selected. */ + KexiFormEventAction::ActionData currentAction() const; + + //! \return the \a KexiMainWindow instance. + KexiMainWindow* mainWin() const; + + virtual bool eventFilter(QObject *o, QEvent *e); + + protected slots: + void slotActionCategorySelected(QListViewItem* item); + void slotKActionItemExecuted(QListViewItem*); + void slotKActionItemSelected(QListViewItem*); + void slotActionToExecuteItemExecuted(QListViewItem* item); + void slotActionToExecuteItemSelected(QListViewItem*); + void slotCurrentFormActionItemExecuted(QListViewItem*); + void slotCurrentFormActionItemSelected(QListViewItem*); + void slotItemForOpeningOrExecutingSelected(KexiPart::Item* item); + + protected: + void updateOKButtonStatus(); + + class KexiActionSelectionDialogPrivate; + KexiActionSelectionDialogPrivate* d; +}; + +#endif diff --git a/kexi/plugins/forms/kexiactionselectiondialog_p.h b/kexi/plugins/forms/kexiactionselectiondialog_p.h new file mode 100644 index 00000000..51f5c369 --- /dev/null +++ b/kexi/plugins/forms/kexiactionselectiondialog_p.h @@ -0,0 +1,51 @@ +/* This file is part of the KDE project + Copyright (C) 2005-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 KEXIACTIONSELECTIONDIALOG_P_H +#define KEXIACTIONSELECTIONDIALOG_P_H + +#include <klistview.h> + +//! @internal +class ActionsListViewBase : public KListView +{ + public: + ActionsListViewBase(QWidget* parent); + virtual ~ActionsListViewBase(); + + //! \return item for action \a actionName + virtual QListViewItem *itemForAction(const QString& actionName); + void selectAction(const QString& actionName); +}; + +//! @internal Used by KActionsListView and CurrentFormActionsListView (in column 2) +class KActionsListViewBase : public ActionsListViewBase +{ + Q_OBJECT + public: + KActionsListViewBase(QWidget* parent, KexiMainWindow* mainWin); + virtual ~KActionsListViewBase(); + void init(); + virtual bool isActionVisible(const char* actionName, int actionCategories) const = 0; + + protected: + KexiMainWindow* m_mainWin; +}; + +#endif diff --git a/kexi/plugins/forms/kexidataawarewidgetinfo.cpp b/kexi/plugins/forms/kexidataawarewidgetinfo.cpp new file mode 100644 index 00000000..a6033c70 --- /dev/null +++ b/kexi/plugins/forms/kexidataawarewidgetinfo.cpp @@ -0,0 +1,43 @@ +/* This file is part of the KDE project + Copyright (C) 2005 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 "kexidataawarewidgetinfo.h" + +KexiDataAwareWidgetInfo::KexiDataAwareWidgetInfo(KFormDesigner::WidgetFactory *f) + : KFormDesigner::WidgetInfo(f) +{ + init(); +} + +KexiDataAwareWidgetInfo::KexiDataAwareWidgetInfo(KFormDesigner::WidgetFactory *f, + const char* parentFactoryName, const char* inheritedClassName) + : KFormDesigner::WidgetInfo(f, parentFactoryName, inheritedClassName) +{ + init(); +} + +KexiDataAwareWidgetInfo::~KexiDataAwareWidgetInfo() +{ +} + +void KexiDataAwareWidgetInfo::init() +{ + setAutoSyncForProperty( "dataSource", false ); + setAutoSyncForProperty( "dataSourceMimeType", false ); +} diff --git a/kexi/plugins/forms/kexidataawarewidgetinfo.h b/kexi/plugins/forms/kexidataawarewidgetinfo.h new file mode 100644 index 00000000..41e67d85 --- /dev/null +++ b/kexi/plugins/forms/kexidataawarewidgetinfo.h @@ -0,0 +1,44 @@ +/* This file is part of the KDE project + Copyright (C) 2005 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 KEXIDATAAWAREWIDGETINFO_H +#define KEXIDATAAWAREWIDGETINFO_H + +#include <formeditor/widgetfactory.h> + +//! A widget info for data-aware widgets +/*! Used within factories just like KFormDesigner::WidgetInfo, + but also predefines specific behaviour, + e.g. sets autoSync flag to false for "dataSource" property. +*/ +class KEXIFORMUTILS_EXPORT KexiDataAwareWidgetInfo : public KFormDesigner::WidgetInfo +{ + public: + KexiDataAwareWidgetInfo(KFormDesigner::WidgetFactory *f); + + KexiDataAwareWidgetInfo(KFormDesigner::WidgetFactory *f, + const char* parentFactoryName, const char* inheritedClassName = 0); + + virtual ~KexiDataAwareWidgetInfo(); + + protected: + void init(); +}; + +#endif diff --git a/kexi/plugins/forms/kexidataprovider.cpp b/kexi/plugins/forms/kexidataprovider.cpp new file mode 100644 index 00000000..6706f838 --- /dev/null +++ b/kexi/plugins/forms/kexidataprovider.cpp @@ -0,0 +1,315 @@ +/* This file is part of the KDE project + Copyright (C) 2005-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 "kexidataprovider.h" + +#include <qwidget.h> +#include <qobjectlist.h> + +#include <kdebug.h> +#include <klocale.h> + +#include <widget/tableview/kexitableitem.h> +#include <widget/tableview/kexitableviewdata.h> +#include <widget/tableview/kexicomboboxbase.h> +#include <kexidb/queryschema.h> +#include <kexiutils/utils.h> + +#include "widgets/kexidbform.h" + +KexiFormDataProvider::KexiFormDataProvider() + : KexiDataItemChangesListener() + , m_mainWidget(0) + , m_duplicatedItems(0) + , m_disableFillDuplicatedDataItems(false) +{ +} + +KexiFormDataProvider::~KexiFormDataProvider() +{ + delete m_duplicatedItems; +} + +void KexiFormDataProvider::setMainDataSourceWidget(QWidget* mainWidget) +{ + m_mainWidget = mainWidget; + m_dataItems.clear(); + m_usedDataSources.clear(); + m_fieldNumbersForDataItems.clear(); + if (!m_mainWidget) + return; + + //find widgets whose will work as data items + QObjectList *l = m_mainWidget->queryList( "QWidget" ); + QObjectListIt it( *l ); + QObject *obj; + QDict<char> tmpSources; + for ( ; (obj = it.current()) != 0; ++it ) { + KexiFormDataItemInterface* const formDataItem = dynamic_cast<KexiFormDataItemInterface*>(obj); + if (!formDataItem) + continue; + if (formDataItem->parentInterface()) //item with parent interface: collect parent instead... + continue; +#if 0 //! @todo reenable when subform is moved to KexiDBForm + KexiDBForm *dbForm = KexiUtils::findParent<KexiDBForm>(obj, "KexiDBForm"); //form's surface... + if (dbForm!=m_mainWidget) //only set data for this form's data items + continue; +#else + //tmp: reject widgets within subforms + if (KexiUtils::findParent<KexiDBForm>(obj, "KexiDBSubForm")) + continue; +#endif + QString dataSource( formDataItem->dataSource().lower() ); + if (dataSource.isEmpty()) + continue; + kexipluginsdbg << obj->name() << endl; + m_dataItems.append( formDataItem ); + formDataItem->installListener( this ); + tmpSources.replace( dataSource, (char*)1 ); + } + delete l; + //now we've got a set (unique list) of field names in tmpSources + //remember it in m_usedDataSources + for (QDictIterator<char> it(tmpSources); it.current(); ++it) { + m_usedDataSources += it.currentKey(); + } +} + +void KexiFormDataProvider::fillDataItems(KexiTableItem& row, bool cursorAtNewRow) +{ + kexipluginsdbg << "KexiFormDataProvider::fillDataItems() cnt=" << row.count() << endl; + for (KexiFormDataItemInterfaceToIntMap::ConstIterator it = m_fieldNumbersForDataItems.constBegin(); + it!=m_fieldNumbersForDataItems.constEnd(); ++it) + { + KexiFormDataItemInterface *itemIface = it.key(); + if (!itemIface->columnInfo()) { + kexipluginsdbg << "KexiFormDataProvider::fillDataItems(): itemIface->columnInfo() == 0" << endl; + continue; + } + //1. Is this a value with a combo box (lookup)? + int indexForVisibleLookupValue = itemIface->columnInfo()->indexForVisibleLookupValue(); + if (indexForVisibleLookupValue<0 && indexForVisibleLookupValue>=(int)row.count()) //sanity + indexForVisibleLookupValue = -1; //no + const QVariant value(row.at(it.data())); + QVariant visibleLookupValue; + if (indexForVisibleLookupValue!=-1 && (int)row.size()>indexForVisibleLookupValue) + visibleLookupValue = row.at(indexForVisibleLookupValue); + kexipluginsdbg << "fill data of '" << itemIface->dataSource() << "' at idx=" << it.data() + << " data=" << value << (indexForVisibleLookupValue!=-1 + ? QString(" SPECIAL: indexForVisibleLookupValue=%1 visibleValue=%2") + .arg(indexForVisibleLookupValue).arg(visibleLookupValue.toString()) + : QString::null) + << endl; + const bool displayDefaultValue = cursorAtNewRow && (value.isNull() && visibleLookupValue.isNull()) + && !itemIface->columnInfo()->field->defaultValue().isNull() + && !itemIface->columnInfo()->field->isAutoIncrement(); //no value to set but there is default value defined + itemIface->setValue( + displayDefaultValue ? itemIface->columnInfo()->field->defaultValue() : value, + QVariant(), /*add*/ + /*!remove old*/false, + indexForVisibleLookupValue==-1 ? 0 : &visibleLookupValue //pass visible value if available + ); + // now disable/enable "display default value" if needed (do it after setValue(), before setValue() turns it off) + if (itemIface->hasDisplayedDefaultValue() != displayDefaultValue) + itemIface->setDisplayDefaultValue( dynamic_cast<QWidget*>(itemIface), displayDefaultValue ); + } +} + +void KexiFormDataProvider::fillDuplicatedDataItems( + KexiFormDataItemInterface* item, const QVariant& value) +{ + if (m_disableFillDuplicatedDataItems) + return; + if (!m_duplicatedItems) { + //build (once) a set of duplicated data items (having the same fields assigned) + //so we can later check if an item is duplicated with a cost of o(1) + QMap<KexiDB::Field*,int> tmpDuplicatedItems; + QMapIterator<KexiDB::Field*,int> it_dup; + for (QPtrListIterator<KexiFormDataItemInterface> it(m_dataItems); it.current(); ++it) { + if (!it.current()->columnInfo() || !it.current()->columnInfo()->field) + continue; + kdDebug() << " ** " << it.current()->columnInfo()->field->name() << endl; + it_dup = tmpDuplicatedItems.find( it.current()->columnInfo()->field ); + uint count; + if (it_dup==tmpDuplicatedItems.end()) + count = 0; + else + count = it_dup.data(); + tmpDuplicatedItems.insert( it.current()->columnInfo()->field, ++count ); + } + m_duplicatedItems = new QPtrDict<char>(101); + for (it_dup = tmpDuplicatedItems.begin(); it_dup!=tmpDuplicatedItems.end(); ++it_dup) { + if (it_dup.data() > 1) { + m_duplicatedItems->insert( it_dup.key(), (char*)1 ); + kexipluginsdbg << "duplicated item: " << static_cast<KexiDB::Field*>(it_dup.key())->name() + << " (" << it_dup.data() << " times)" << endl; + } + } + } + if (item->columnInfo() && m_duplicatedItems->find( item->columnInfo()->field )) { + for (QPtrListIterator<KexiFormDataItemInterface> it(m_dataItems); it.current(); ++it) { + if (it.current()!=item && item->columnInfo()->field == it.current()->columnInfo()->field) { + kexipluginsdbg << "- setting a copy of value for item '" + << dynamic_cast<QObject*>(it.current())->name() << "' == " << value << endl; + it.current()->setValue( value ); + } + } + } +} + +void KexiFormDataProvider::valueChanged(KexiDataItemInterface* item) +{ + Q_UNUSED( item ); +} + +bool KexiFormDataProvider::cursorAtNewRow() const +{ + return false; +} + +void KexiFormDataProvider::invalidateDataSources( const QDict<char>& invalidSources, + KexiDB::QuerySchema* query) +{ + //fill m_fieldNumbersForDataItems mapping from data item to field number + //(needed for fillDataItems) + KexiDB::QueryColumnInfo::Vector fieldsExpanded; +// uint dataFieldsCount; // == fieldsExpanded.count() if query is available or else == m_dataItems.count() + + if (query) { + fieldsExpanded = query->fieldsExpanded( KexiDB::QuerySchema::WithInternalFields ); +// dataFieldsCount = fieldsExpanded.count(); + QMap<KexiDB::QueryColumnInfo*,int> columnsOrder( query->columnsOrder() ); + for (QMapConstIterator<KexiDB::QueryColumnInfo*,int> it = columnsOrder.constBegin(); it!=columnsOrder.constEnd(); ++it) { + kexipluginsdbg << "query->columnsOrder()[ " << it.key()->field->name() << " ] = " << it.data() << endl; + } + for (QPtrListIterator<KexiFormDataItemInterface> it(m_dataItems); it.current(); ++it) { + KexiFormDataItemInterface *item = it.current(); + KexiDB::QueryColumnInfo* ci = query->columnInfo( it.current()->dataSource() ); + int index = ci ? columnsOrder[ ci ] : -1; + kexipluginsdbg << "query->columnsOrder()[ " << (ci ? ci->field->name() : "") << " ] = " << index + << " (dataSource: " << item->dataSource() << ", name=" << dynamic_cast<QObject*>(item)->name() << ")" << endl; + if (index!=-1 && !m_fieldNumbersForDataItems[ item ]) + m_fieldNumbersForDataItems.insert( item, index ); + //todo + //WRONG: not only used data sources can be fetched! + // m_fieldNumbersForDataItems.insert( it.current(), + // m_usedDataSources.findIndex(it.current()->dataSource().lower()) ); + } + } + else {//!query +// dataFieldsCount = m_dataItems.count(); + } + +#if 0 //moved down + //in 'newIndices' let's collect new indices for every data source + foreach(QValueList<uint>::ConstIterator, it, invalidSources) { + //all previous indices have corresponding data source +// for (; i < (*it); i++) { +// newIndices[i] = number++; + //kexipluginsdbg << "invalidateDataSources(): " << i << " -> " << number-1 << endl; +// } + //this index have no corresponding data source +// newIndices[i]=-1; + KexiFormDataItemInterface *item = m_dataItems.at( *it ); + if (item) + item->setInvalidState( QString::fromLatin1("#") + i18n("NAME") + QString::fromLatin1("?") ); + m_dataItems.remove(*it); + kexipluginsdbg << "invalidateDataSources(): " << (*it) << " -> " << -1 << endl; +// i++; + } +#endif + //fill remaining part of the vector +// for (; i < dataFieldsCount; i++) { //m_dataItems.count(); i++) { + //newIndices[i] = number++; + //kexipluginsdbg << "invalidateDataSources(): " << i << " -> " << number-1 << endl; + //} + +#if 0 + //recreate m_fieldNumbersForDataItems and mark widgets with invalid data sources + KexiFormDataItemInterfaceToIntMap newFieldNumbersForDataItems; + foreach(KexiFormDataItemInterfaceToIntMap::ConstIterator, it, m_fieldNumbersForDataItems) { + bool ok; + const int newIndex = newIndices.at( it.data(), &ok ); + if (ok && newIndex!=-1) { + kexipluginsdbg << "invalidateDataSources(): " << it.key()->dataSource() << ": " << it.data() << " -> " << newIndex << endl; + newFieldNumbersForDataItems.replace(it.key(), newIndex); + } + else { + kexipluginsdbg << "invalidateDataSources(): removing " << it.key()->dataSource() << endl; + m_dataItems.remove(it.key()); + it.key()->setInvalidState( QString::fromLatin1("#") + i18n("NAME") + QString::fromLatin1("?") ); + } + } +#endif +// m_fieldNumbersForDataItems = newFieldNumbersForDataItems; + + //update data sources set (some of them may be removed) + QDict<char> tmpUsedDataSources(1013); + + if (query) + query->debug(); + + //if (query && m_dataItems.count()!=query->fieldCount()) { + // kdWarning() << "KexiFormDataProvider::invalidateDataSources(): m_dataItems.count()!=query->fieldCount() (" + // << m_dataItems.count() << "," << query->fieldCount() << ")" << endl; + //} + //i = 0; + m_disableFillDuplicatedDataItems = true; // temporary disable fillDuplicatedDataItems() + // because setColumnInfo() can activate it + for (QPtrListIterator<KexiFormDataItemInterface> it(m_dataItems); it.current();) { + KexiFormDataItemInterface * item = it.current(); + if (invalidSources[ item->dataSource().lower() ]) { + item->setInvalidState( QString::fromLatin1("#") + i18n("NAME") + QString::fromLatin1("?") ); + m_dataItems.remove(item); + continue; + } + uint fieldNumber = m_fieldNumbersForDataItems[ item ]; + if (query) { + KexiDB::QueryColumnInfo *ci = fieldsExpanded[fieldNumber]; + item->setColumnInfo(ci); + kexipluginsdbg << "- item=" << dynamic_cast<QObject*>(item)->name() + << " dataSource=" << item->dataSource() + << " field=" << ci->field->name() << endl; + const int indexForVisibleLookupValue = ci->indexForVisibleLookupValue(); + if (-1 != indexForVisibleLookupValue && indexForVisibleLookupValue < (int)fieldsExpanded.count()) { + //there's lookup column defined: set visible column as well + KexiDB::QueryColumnInfo *visibleColumnInfo = fieldsExpanded[ indexForVisibleLookupValue ]; + if (visibleColumnInfo) { + item->setVisibleColumnInfo( visibleColumnInfo ); + if (dynamic_cast<KexiComboBoxBase*>(item) && m_mainWidget + && dynamic_cast<KexiComboBoxBase*>(item)->internalEditor()) + { + // m_mainWidget (dbform) should filter the (just created using setVisibleColumnInfo()) + // combo box' internal editor (actually, only if the combo is in 'editable' mode) + dynamic_cast<KexiComboBoxBase*>(item)->internalEditor()->installEventFilter(m_mainWidget); + } + kexipluginsdbg << " ALSO SET visibleColumn=" << visibleColumnInfo->debugString() + << "\n at position " << indexForVisibleLookupValue << endl; + } + } + } + tmpUsedDataSources.replace( item->dataSource().lower(), (char*)1 ); + ++it; + } + m_disableFillDuplicatedDataItems = false; + m_usedDataSources.clear(); + foreach_list(QDictIterator<char>, it, tmpUsedDataSources) { + m_usedDataSources += it.currentKey(); + } +} diff --git a/kexi/plugins/forms/kexidataprovider.h b/kexi/plugins/forms/kexidataprovider.h new file mode 100644 index 00000000..64019842 --- /dev/null +++ b/kexi/plugins/forms/kexidataprovider.h @@ -0,0 +1,95 @@ +/* This file is part of the KDE project + Copyright (C) 2005 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 KEXIFORMDATAPROVIDER_H +#define KEXIFORMDATAPROVIDER_H + +#include "kexiformdataiteminterface.h" +#include <qptrdict.h> +#include <qdict.h> + +class KexiTableItem; +namespace KexiDB { + class QuerySchema; +} + +//! @short The KexiFormDataProvider class is a data provider for Kexi Forms +/*! This provider collects data-aware widgets using setMainWidget(). + Then, usedDataSources() unique list of required field names is available. + On every call of fillDataItems() method, the provider will fill data items + with appropriate data from a database cursor. + + Field names are collected effectively, so eg. having widgets using data sources: + ("name", "surname", "surname", "name") - "name" and "surname" repeated - will only + return ("name", "surname") list, so the cursor's query can be simplified + and thus more effective. +*/ +class KEXIFORMUTILS_EXPORT KexiFormDataProvider : public KexiDataItemChangesListener +{ + public: + KexiFormDataProvider(); + virtual ~KexiFormDataProvider(); + + /*! sets \a mainWidget to be a main widget for this data provider. + Also find widgets whose will work as data items + (all of them must implement KexiFormDataItemInterface), so these could be + filled with data on demand. */ + void setMainDataSourceWidget(QWidget* mainWidget); + + QStringList usedDataSources() const { return m_usedDataSources; } + + //unused QPtrList<KexiFormDataItemInterface>& dataItems() { return m_dataItems; } + + /*! Fills data items with appropriate data fetched from \a cursor. + \a newRowEditing == true means that we are at new (not yet inserted) database row. */ + void fillDataItems(KexiTableItem& row, bool cursorAtNewRow); + + /*! Implementation for KexiDataItemChangesListener. + Reaction for change of \a item. Does nothing here. */ + virtual void valueChanged(KexiDataItemInterface* item); + + /*! Implementation for KexiDataItemChangesListener. + Implement this to return information whether we're currently at new row or now. + This can be used e.g. by data-aware widgets to determine if "(autonumber)" + label should be displayed. Returns false here. */ + virtual bool cursorAtNewRow() const; + + /*! Invalidates data sources collected by this provided. + \a invalidSources is the set of data sources that should + be omitted for fillDataItems(). + Used by KexiFormView::initDataSource(). */ + void invalidateDataSources( const QDict<char>& invalidSources, + KexiDB::QuerySchema* query = 0 ); + + /*! Fills the same data provided by \a value to every data item (other than \a item) + having the same data source as \a item. This method is called immediately when + \a value is changed, so duplicated data items are quickly updated. */ + void fillDuplicatedDataItems(KexiFormDataItemInterface* item, const QVariant& value); + + protected: + QWidget *m_mainWidget; + QPtrDict<char> *m_duplicatedItems; + typedef QMap<KexiFormDataItemInterface*,uint> KexiFormDataItemInterfaceToIntMap; + QPtrList<KexiFormDataItemInterface> m_dataItems; + QStringList m_usedDataSources; + KexiFormDataItemInterfaceToIntMap m_fieldNumbersForDataItems; + bool m_disableFillDuplicatedDataItems : 1; +}; + +#endif diff --git a/kexi/plugins/forms/kexidatasourcepage.cpp b/kexi/plugins/forms/kexidatasourcepage.cpp new file mode 100644 index 00000000..6c0de830 --- /dev/null +++ b/kexi/plugins/forms/kexidatasourcepage.cpp @@ -0,0 +1,471 @@ +/* This file is part of the KDE project + Copyright (C) 2005 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 "kexidatasourcepage.h" + +#include <qlabel.h> +#include <qlayout.h> +#include <qtooltip.h> +#include <qheader.h> + +#include <kiconloader.h> +#include <klocale.h> +#include <ktoolbarbutton.h> +#include <kdebug.h> +#include <kpopupmenu.h> + +#include <widget/kexipropertyeditorview.h> +#include <widget/kexidatasourcecombobox.h> +#include <widget/kexifieldlistview.h> +#include <widget/kexifieldcombobox.h> +#include <widget/kexismalltoolbutton.h> +#include <kexidb/connection.h> +#include <kexiproject.h> + +#include <formeditor/commands.h> + +#include <koproperty/property.h> +#include <koproperty/utils.h> + +KexiDataSourcePage::KexiDataSourcePage(QWidget *parent, const char *name) + : QWidget(parent, name) + , m_insideClearDataSourceSelection(false) +{ + QVBoxLayout *vlyr = new QVBoxLayout(this); + m_objectInfoLabel = new KexiObjectInfoLabel(this, "KexiObjectInfoLabel"); + vlyr->addWidget(m_objectInfoLabel); + + m_noDataSourceAvailableSingleText = i18n("No data source could be assigned for this widget."); + m_noDataSourceAvailableMultiText = i18n("No data source could be assigned for multiple widgets."); + + vlyr->addSpacing(8); + + //Section 1: Form's/Widget's Data Source + KoProperty::GroupContainer *container = new KoProperty::GroupContainer(i18n("Data Source"), this); + vlyr->addWidget(container); + + QWidget *contents = new QWidget(container); + container->setContents(contents); + QVBoxLayout *contentsVlyr = new QVBoxLayout(contents); + + m_noDataSourceAvailableLabel = new QLabel(m_noDataSourceAvailableSingleText, contents); + m_noDataSourceAvailableLabel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); + m_noDataSourceAvailableLabel->setMargin(2); + m_noDataSourceAvailableLabel->setAlignment(Qt::WordBreak | Qt::AlignBottom | Qt::AlignLeft); + contentsVlyr->addWidget(m_noDataSourceAvailableLabel); + + //-Widget's Data Source + QHBoxLayout *hlyr = new QHBoxLayout(contentsVlyr); +#if 0 +//! @todo unhide this when expression work +// m_widgetDSLabel = new QLabel(i18n("Table Field, Query Field or Expression", "Source field or expression:"), this); +#else + m_widgetDSLabel = new QLabel(i18n("Table Field or Query Field", "Widget's data source:"), contents); +#endif + m_widgetDSLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); + m_widgetDSLabel->setMargin(2); + m_widgetDSLabel->setMinimumHeight(IconSize(KIcon::Small)+4); + m_widgetDSLabel->setAlignment(AlignLeft|AlignBottom); + hlyr->addWidget(m_widgetDSLabel); + + m_clearWidgetDSButton = new KexiSmallToolButton(contents, QString::null, "clear_left", "clearWidgetDSButton"); + m_clearWidgetDSButton->setMinimumHeight(m_widgetDSLabel->minimumHeight()); + QToolTip::add(m_clearWidgetDSButton, i18n("Clear widget's data source")); + hlyr->addWidget(m_clearWidgetDSButton); + connect(m_clearWidgetDSButton, SIGNAL(clicked()), this, SLOT(clearWidgetDataSourceSelection())); + + m_sourceFieldCombo = new KexiFieldComboBox(contents, "sourceFieldCombo"); + m_widgetDSLabel->setBuddy(m_sourceFieldCombo); + contentsVlyr->addWidget(m_sourceFieldCombo); + +/* m_dataSourceSeparator = new QFrame(contents); + m_dataSourceSeparator->setFrameShape(QFrame::HLine); + m_dataSourceSeparator->setFrameShadow(QFrame::Sunken); + contentsVlyr->addWidget(m_dataSourceSeparator);*/ + + contentsVlyr->addSpacing(8); + + //- Form's Data Source + hlyr = new QHBoxLayout(contentsVlyr); + m_dataSourceLabel = new QLabel(i18n("Form's data source:"), contents); + m_dataSourceLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); + m_dataSourceLabel->setMargin(2); + m_dataSourceLabel->setMinimumHeight(IconSize(KIcon::Small)+4); + m_dataSourceLabel->setAlignment(AlignLeft|AlignBottom); + hlyr->addWidget(m_dataSourceLabel); + + m_gotoButton = new KexiSmallToolButton(contents, QString::null, "goto", "gotoButton"); + m_gotoButton->setMinimumHeight(m_dataSourceLabel->minimumHeight()); + QToolTip::add(m_gotoButton, i18n("Go to selected form's data source")); + hlyr->addWidget(m_gotoButton); + connect(m_gotoButton, SIGNAL(clicked()), this, SLOT(slotGotoSelected())); + + m_clearDSButton = new KexiSmallToolButton(contents, QString::null, "clear_left", "clearDSButton"); + m_clearDSButton->setMinimumHeight(m_dataSourceLabel->minimumHeight()); + QToolTip::add(m_clearDSButton, i18n("Clear form's data source")); + hlyr->addWidget(m_clearDSButton); + connect(m_clearDSButton, SIGNAL(clicked()), this, SLOT(clearDataSourceSelection())); + + m_dataSourceCombo = new KexiDataSourceComboBox(contents, "dataSourceCombo"); + m_dataSourceLabel->setBuddy(m_dataSourceCombo); + contentsVlyr->addWidget(m_dataSourceCombo); + +#ifdef KEXI_NO_AUTOFIELD_WIDGET + m_availableFieldsLabel = 0; + m_addField = 0; +// m_fieldListView = 0; + vlyr->addStretch(); +#else + vlyr->addSpacing(fontMetrics().height()); +/* QFrame *separator = new QFrame(this); + separator->setFrameShape(QFrame::HLine); + separator->setFrameShadow(QFrame::Sunken); + vlyr->addWidget(separator);*/ +/* + KPopupTitle *title = new KPopupTitle(this); + title->setTitle(i18n("Inserting fields")); + vlyr->addWidget(title); + vlyr->addSpacing(4);*/ + + + //2. Inserting fields + container = new KoProperty::GroupContainer(i18n("Inserting Fields"), this); + vlyr->addWidget(container, 1); + + //helper info +//! @todo allow to hide such helpers by adding global option + contents = new QWidget(container); + container->setContents(contents); + contentsVlyr = new QVBoxLayout(contents); + hlyr = new QHBoxLayout(contentsVlyr); + m_mousePointerLabel = new QLabel(contents); + hlyr->addWidget(m_mousePointerLabel); + m_mousePointerLabel->setPixmap( SmallIcon("mouse_pointer") ); + m_mousePointerLabel->setFixedWidth( m_mousePointerLabel->pixmap() ? m_mousePointerLabel->pixmap()->width() : 0); + m_availableFieldsDescriptionLabel = new QLabel( + i18n("Select fields from the list below and drag them onto a form or click the \"Insert\" button"), contents); + m_availableFieldsDescriptionLabel->setAlignment( Qt::AlignAuto | Qt::WordBreak ); + hlyr->addWidget(m_availableFieldsDescriptionLabel); + + //Available Fields + contentsVlyr->addSpacing(4); + hlyr = new QHBoxLayout(contentsVlyr); + m_availableFieldsLabel = new QLabel(i18n("Available fields:"), contents); + m_availableFieldsLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); + m_availableFieldsLabel->setMargin(2); + m_availableFieldsLabel->setMinimumHeight(IconSize(KIcon::Small)); + hlyr->addWidget(m_availableFieldsLabel); + + m_addField = new KexiSmallToolButton(contents, i18n("Insert selected field into form", "Insert"), + "add_field", "addFieldButton"); + m_addField->setMinimumHeight(m_availableFieldsLabel->minimumHeight()); +// m_addField->setTextPosition(QToolButton::Right); + m_addField->setFocusPolicy(StrongFocus); + QToolTip::add(m_addField, i18n("Insert selected fields into form")); + hlyr->addWidget(m_addField); + connect(m_addField, SIGNAL(clicked()), this, SLOT(slotInsertSelectedFields())); + + m_fieldListView = new KexiFieldListView(contents, "fieldListView", + KexiFieldListView::ShowDataTypes | KexiFieldListView::AllowMultiSelection ); + m_fieldListView->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding)); + m_availableFieldsLabel->setBuddy(m_fieldListView); + contentsVlyr->addWidget(m_fieldListView, 1); + connect(m_fieldListView, SIGNAL(selectionChanged()), this, SLOT(slotFieldListViewSelectionChanged())); + connect(m_fieldListView, SIGNAL(fieldDoubleClicked(const QString&, const QString&, const QString&)), + this, SLOT(slotFieldDoubleClicked(const QString&, const QString&, const QString&))); +#endif + + vlyr->addStretch(1); + + connect(m_dataSourceCombo, SIGNAL(textChanged(const QString &)), this, SLOT(slotDataSourceTextChanged(const QString &))); + connect(m_dataSourceCombo, SIGNAL(dataSourceChanged()), this, SLOT(slotDataSourceChanged())); + connect(m_sourceFieldCombo, SIGNAL(selected()), this, SLOT(slotFieldSelected())); + + clearDataSourceSelection(); + slotFieldListViewSelectionChanged(); +} + +KexiDataSourcePage::~KexiDataSourcePage() +{ +} + +void KexiDataSourcePage::setProject(KexiProject *prj) +{ + m_sourceFieldCombo->setProject(prj); + m_dataSourceCombo->setProject(prj); +} + +void KexiDataSourcePage::clearDataSourceSelection(bool alsoClearComboBox) +{ + if (m_insideClearDataSourceSelection) + return; + m_insideClearDataSourceSelection = true; + if (alsoClearComboBox && !m_dataSourceCombo->selectedName().isEmpty()) + m_dataSourceCombo->setDataSource("", ""); +// if (!m_dataSourceCombo->currentText().isEmpty()) { +// m_dataSourceCombo->setCurrentText(""); +// emit m_dataSourceCombo->dataSourceSelected(); +// } + m_clearDSButton->setEnabled(false); + m_gotoButton->setEnabled(false); +#ifndef KEXI_NO_AUTOFIELD_WIDGET + m_addField->setEnabled(false); + m_fieldListView->clear(); +#endif + m_insideClearDataSourceSelection = false; +} + +void KexiDataSourcePage::clearWidgetDataSourceSelection() +{ + if (!m_sourceFieldCombo->currentText().isEmpty()) { + m_sourceFieldCombo->setCurrentText(""); + m_sourceFieldCombo->setFieldOrExpression(QString::null); + slotFieldSelected(); + } + m_clearWidgetDSButton->setEnabled(false); +} + +void KexiDataSourcePage::slotGotoSelected() +{ + QCString mime = m_dataSourceCombo->selectedMimeType().latin1(); + if (mime=="kexi/table" || mime=="kexi/query") { + if (m_dataSourceCombo->isSelectionValid()) + emit jumpToObjectRequested(mime, m_dataSourceCombo->selectedName().latin1()); + } +} + +void KexiDataSourcePage::slotInsertSelectedFields() +{ +#ifndef KEXI_NO_AUTOFIELD_WIDGET + QStringList selectedFieldNames(m_fieldListView->selectedFieldNames()); + if (selectedFieldNames.isEmpty()) + return; + + emit insertAutoFields(m_fieldListView->schema()->table() ? "kexi/table" : "kexi/query", + m_fieldListView->schema()->name(), selectedFieldNames); +#endif +} + +void KexiDataSourcePage::slotFieldDoubleClicked(const QString& sourceMimeType, const QString& sourceName, + const QString& fieldName) +{ +#ifndef KEXI_NO_AUTOFIELD_WIDGET + QStringList selectedFields; + selectedFields.append(fieldName); + emit insertAutoFields(sourceMimeType, sourceName, selectedFields); +#endif +} + +void KexiDataSourcePage::slotDataSourceTextChanged(const QString & string) +{ + Q_UNUSED(string); + const bool enable = m_dataSourceCombo->isSelectionValid(); //!string.isEmpty() && m_dataSourceCombo->selectedName() == string.latin1(); + if (!enable) { + clearDataSourceSelection( m_dataSourceCombo->selectedName().isEmpty()/*alsoClearComboBox*/ ); + } + updateSourceFieldWidgetsAvailability(); +/*#ifndef KEXI_NO_AUTOFIELD_WIDGET + m_fieldListView->setEnabled(enable); +// m_addField->setEnabled(enable); + m_availableFieldsLabel->setEnabled(enable); +#endif*/ +} + +void KexiDataSourcePage::slotDataSourceChanged() +{ + if (!m_dataSourceCombo->project()) + return; + QCString mime = m_dataSourceCombo->selectedMimeType().latin1(); + bool dataSourceFound = false; + QCString name = m_dataSourceCombo->selectedName().latin1(); + if ((mime=="kexi/table" || mime=="kexi/query") && m_dataSourceCombo->isSelectionValid()) { + KexiDB::TableOrQuerySchema *tableOrQuery = new KexiDB::TableOrQuerySchema( + m_dataSourceCombo->project()->dbConnection(), name, mime=="kexi/table"); + if (tableOrQuery->table() || tableOrQuery->query()) { +#ifdef KEXI_NO_AUTOFIELD_WIDGET + m_tableOrQuerySchema = tableOrQuery; +#else + m_fieldListView->setSchema( tableOrQuery ); +#endif + dataSourceFound = true; + m_sourceFieldCombo->setTableOrQuery(name, mime=="kexi/table"); + } + else { + delete tableOrQuery; + } + } + if (!dataSourceFound) { + m_sourceFieldCombo->setTableOrQuery("", true); + } + //if (m_sourceFieldCombo->hasFocus()) +// m_dataSourceCombo->setFocus(); + m_clearDSButton->setEnabled(dataSourceFound); + m_gotoButton->setEnabled(dataSourceFound); + if (dataSourceFound) { + slotFieldListViewSelectionChanged(); + } else { +#ifndef KEXI_NO_AUTOFIELD_WIDGET + m_addField->setEnabled(false); +#endif + } + updateSourceFieldWidgetsAvailability(); + emit formDataSourceChanged(mime, name); +} + +void KexiDataSourcePage::slotFieldSelected() +{ + KexiDB::Field::Type dataType = KexiDB::Field::InvalidType; +#ifdef KEXI_NO_AUTOFIELD_WIDGET + KexiDB::Field *field = m_tableOrQuerySchema->field( m_sourceFieldCombo->fieldOrExpression() ); //temp +#else +//! @todo this should also work for expressions + KexiDB::Field *field = m_fieldListView->schema()->field( m_sourceFieldCombo->fieldOrExpression() ); +#endif + if (field) + dataType = field->type(); + + m_clearWidgetDSButton->setEnabled( !m_sourceFieldCombo->fieldOrExpression().isEmpty() ); + + emit dataSourceFieldOrExpressionChanged( + m_sourceFieldCombo->fieldOrExpression(), + m_sourceFieldCombo->fieldOrExpressionCaption(), + dataType + ); +} + +void KexiDataSourcePage::setDataSource(const QCString& mimeType, const QCString& name) +{ + m_dataSourceCombo->setDataSource(mimeType, name); +} + +void KexiDataSourcePage::assignPropertySet(KoProperty::Set* propertySet) +{ + QCString objectName; + if (propertySet && propertySet->contains("name")) + objectName = (*propertySet)["name"].value().toCString(); + if (!objectName.isEmpty() && objectName == m_currentObjectName) + return; //the same object + m_currentObjectName = objectName; + + QCString objectClassName; + if (propertySet && propertySet->contains("this:className")) + objectClassName = (*propertySet)["this:className"].value().toCString(); +/*moved if (propertySet) { + QCString iconName; + QString objectClassString; + if (propertySet->contains("this:iconName")) + iconName = (*propertySet)["this:iconName"].value().toCString(); + if (propertySet->contains("this:classString")) + objectClassString = (*propertySet)["this:classString"].value().toString(); + m_objectInfoLabel->setObjectName(objectName); + m_objectInfoLabel->setObjectClassIcon(iconName); + m_objectInfoLabel->setObjectClassName(objectClassString); + if (propertySet->contains("this:className")) + objectClassName = (*propertySet)["this:className"].value().toCString(); + }*/ + KexiPropertyEditorView::updateInfoLabelForPropertySet( + m_objectInfoLabel, propertySet); + + const bool isForm = objectClassName=="KexiDBForm"; +// kdDebug() << "objectClassName=" << objectClassName << endl; +// { +/* //this is top level form's surface: data source means table or query + QCString dataSourceMimeType, dataSource; + if (buffer->hasProperty("dataSourceMimeType")) + dataSourceMimeType = (*buffer)["dataSourceMimeType"].value().toCString(); + if (buffer->hasProperty("dataSource")) + dataSource = (*buffer)["dataSource"].value().toCString(); + m_dataSourceCombo->setDataSource(dataSourceMimeType, dataSource);*/ +// } +// else { + + const bool multipleSelection = objectClassName=="special:multiple"; + const bool hasDataSourceProperty = propertySet && propertySet->contains("dataSource") && !multipleSelection; + + if (!isForm) { + //this is a widget + QCString dataSource; + if (hasDataSourceProperty) { + if (propertySet) + dataSource = (*propertySet)["dataSource"].value().toCString(); + m_noDataSourceAvailableLabel->hide(); + m_sourceFieldCombo->setFieldOrExpression(dataSource); + m_sourceFieldCombo->setEnabled(true); + m_clearWidgetDSButton->setEnabled(!m_sourceFieldCombo->currentText().isEmpty()); + m_widgetDSLabel->show(); + m_clearWidgetDSButton->show(); + m_sourceFieldCombo->show(); +// m_dataSourceSeparator->hide(); + updateSourceFieldWidgetsAvailability(); + } + } + + if (isForm) { + m_noDataSourceAvailableLabel->hide(); +// m_dataSourceSeparator->hide(); + } + else if (!hasDataSourceProperty) { + if (multipleSelection) + m_noDataSourceAvailableLabel->setText(m_noDataSourceAvailableMultiText); + else + m_noDataSourceAvailableLabel->setText(m_noDataSourceAvailableSingleText); + m_noDataSourceAvailableLabel->show(); +// m_dataSourceSeparator->show(); + //make 'No data source could be assigned' label's height the same as the 'source field' combo+label + m_noDataSourceAvailableLabel->setMinimumHeight(m_widgetDSLabel->height() + + m_sourceFieldCombo->height()/*-m_dataSourceSeparator->height()*/); + m_sourceFieldCombo->setCurrentText(""); + } + + if (isForm || !hasDataSourceProperty) { + //no source field can be set + m_widgetDSLabel->hide(); + m_clearWidgetDSButton->hide(); + m_sourceFieldCombo->hide(); + } +} + +void KexiDataSourcePage::slotFieldListViewSelectionChanged() +{ +#ifndef KEXI_NO_AUTOFIELD_WIDGET + //update "add field" button's state + for (QListViewItemIterator it(m_fieldListView); it.current(); ++it) { + if (it.current()->isSelected()) { + m_addField->setEnabled(true); + return; + } + } + m_addField->setEnabled(false); +#endif +} + +void KexiDataSourcePage::updateSourceFieldWidgetsAvailability() +{ + const bool hasDataSource = m_dataSourceCombo->isSelectionValid(); //!m_dataSourceCombo->selectedName().isEmpty(); + m_sourceFieldCombo->setEnabled( hasDataSource ); + m_widgetDSLabel->setEnabled( hasDataSource ); +#ifndef KEXI_NO_AUTOFIELD_WIDGET + m_fieldListView->setEnabled( hasDataSource ); + m_availableFieldsLabel->setEnabled( hasDataSource ); + m_mousePointerLabel->setEnabled( hasDataSource ); + m_availableFieldsDescriptionLabel->setEnabled( hasDataSource ); +#endif +} + +#include "kexidatasourcepage.moc" diff --git a/kexi/plugins/forms/kexidatasourcepage.h b/kexi/plugins/forms/kexidatasourcepage.h new file mode 100644 index 00000000..0f113aa7 --- /dev/null +++ b/kexi/plugins/forms/kexidatasourcepage.h @@ -0,0 +1,112 @@ +/* This file is part of the KDE project + Copyright (C) 2005 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 KEXIDATASOURCEPAGE_H +#define KEXIDATASOURCEPAGE_H + +#include <qwidget.h> +#include <kexidb/field.h> +#include <kexidb/utils.h> +#include <koproperty/set.h> + +class KCommand; +class KexiObjectInfoLabel; +class KexiDataSourceComboBox; +class KexiFieldComboBox; +class KexiFieldListView; +class KexiProject; +class QToolButton; +class QLabel; +class QFrame; + +//! A page within form designer's property tabbed pane, providing data source editor +class KEXIFORMUTILS_EXPORT KexiDataSourcePage : public QWidget +{ + Q_OBJECT + + public: + KexiDataSourcePage(QWidget *parent, const char *name = 0); + virtual ~KexiDataSourcePage(); + + KexiDataSourceComboBox* dataSourceCombo() const { return m_dataSourceCombo; } + KexiObjectInfoLabel* objectInfoLabel() const { return m_objectInfoLabel; } + + public slots: + void setProject(KexiProject *prj); + void clearDataSourceSelection(bool alsoClearComboBox = true); + void clearWidgetDataSourceSelection(); + + //! Sets data source of a currently selected form. + //! This is performed on form initialization and on activating. + void setDataSource(const QCString& mimeType, const QCString& name); + + //! Receives a pointer to a new property \a set (from KexiFormView::managerPropertyChanged()) + void assignPropertySet(KoProperty::Set* propertySet); + + signals: + //! Signal emitted when helper button 'go to selected data source' is clicked. + void jumpToObjectRequested(const QCString& mime, const QCString& name); + + //! Signal emitted when form's data source has been changed. It's connected to the Form Manager. + void formDataSourceChanged(const QCString& mime, const QCString& name); + + /*! Signal emitted when current widget's data source (field/expression) + has been changed. It's connected to the Form Manager. + \a caption for this field is also provided (e.g. AutoField form widget use it) */ + void dataSourceFieldOrExpressionChanged(const QString& string, const QString& caption, + KexiDB::Field::Type type); + + /*! Signal emitted when 'insert fields' button has been clicked */ + void insertAutoFields(const QString& sourceMimeType, const QString& sourceName, + const QStringList& fields); + + protected slots: + void slotDataSourceTextChanged(const QString & string); + void slotDataSourceChanged(); + void slotFieldSelected(); + void slotGotoSelected(); + void slotInsertSelectedFields(); + void slotFieldListViewSelectionChanged(); + void slotFieldDoubleClicked(const QString& sourceMimeType, const QString& sourceName, + const QString& fieldName); + + protected: + void updateSourceFieldWidgetsAvailability(); + + KexiFieldComboBox *m_sourceFieldCombo; + KexiObjectInfoLabel *m_objectInfoLabel; + KexiDataSourceComboBox* m_dataSourceCombo; + QLabel *m_dataSourceLabel, *m_noDataSourceAvailableLabel, + *m_widgetDSLabel, *m_availableFieldsLabel, + *m_mousePointerLabel, *m_availableFieldsDescriptionLabel; + QToolButton *m_clearWidgetDSButton, *m_clearDSButton, *m_gotoButton, *m_addField; + QFrame *m_dataSourceSeparator; + QString m_noDataSourceAvailableSingleText, m_noDataSourceAvailableMultiText; + bool m_insideClearDataSourceSelection : 1; +#ifdef KEXI_NO_AUTOFIELD_WIDGET + KexiDB::TableOrQuerySchema *m_tableOrQuerySchema; //!< temp. +#else + KexiFieldListView* m_fieldListView; +#endif + + //! Used only in assignPropertySet() to check whether we already have the set assigned + QCString m_currentObjectName; + //QGuardedPtr<KoProperty::Set> m_propertySet; +}; + +#endif diff --git a/kexi/plugins/forms/kexidbfactory.cpp b/kexi/plugins/forms/kexidbfactory.cpp new file mode 100644 index 00000000..4ab05d76 --- /dev/null +++ b/kexi/plugins/forms/kexidbfactory.cpp @@ -0,0 +1,713 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr> + 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 <qpopupmenu.h> +#include <qscrollview.h> +#include <qcursor.h> +#include <qpainter.h> +#include <qstyle.h> + +#include <kgenericfactory.h> +#include <klocale.h> +#include <kdebug.h> +#include <kiconloader.h> +#include <kactioncollection.h> +#include <kstdaction.h> + +#include <formeditor/container.h> +#include <formeditor/form.h> +#include <formeditor/formIO.h> +#include <formeditor/formmanager.h> +#include <formeditor/objecttree.h> +#include <formeditor/utils.h> +#include <kexidb/utils.h> +#include <kexidb/connection.h> +#include <kexipart.h> +#include <formeditor/widgetlibrary.h> +#include <kexigradientwidget.h> +#include <keximainwindow.h> +#include <kexiutils/utils.h> +#include <widget/kexicustompropertyfactory.h> +#include <widget/utils/kexicontextmenuutils.h> + +#include "kexiformview.h" +#include "widgets/kexidbautofield.h" +#include "widgets/kexidbcheckbox.h" +#include "widgets/kexidbimagebox.h" +//#include "widgets/kexidbdoublespinbox.h" +//#include "widgets/kexidbintspinbox.h" +#include "widgets/kexiframe.h" +#include "widgets/kexidblabel.h" +#include "widgets/kexidblineedit.h" +#include "widgets/kexidbtextedit.h" +#include "widgets/kexidbcombobox.h" +#include "widgets/kexipushbutton.h" +#include "widgets/kexidbform.h" +#include "widgets/kexidbsubform.h" +#include "kexidataawarewidgetinfo.h" + +#include "kexidbfactory.h" +#include <core/kexi.h> + + +////////////////////////////////////////// + +KexiDBFactory::KexiDBFactory(QObject *parent, const char *name, const QStringList &) + : KFormDesigner::WidgetFactory(parent, name) +{ + KFormDesigner::WidgetInfo *wi; + wi = new KexiDataAwareWidgetInfo(this); + wi->setPixmap("form"); + wi->setClassName("KexiDBForm"); + wi->setName(i18n("Form")); + wi->setNamePrefix( + i18n("Widget name. This string will be used to name widgets of this class. " + "It must _not_ contain white spaces and non latin1 characters.", "form")); + wi->setDescription(i18n("A data-aware form widget")); + addClass(wi); + +#ifndef KEXI_NO_SUBFORM + wi = new KexiDataAwareWidgetInfo(this); + wi->setPixmap("subform"); + wi->setClassName("KexiDBSubForm"); + wi->addAlternateClassName("KexiSubForm", true/*override*/); //older + wi->setName(i18n("Sub Form")); + wi->setNamePrefix( + i18n("Widget name. This string will be used to name widgets of this class. " + "It must _not_ contain white spaces and non latin1 characters.", "subForm")); + wi->setDescription(i18n("A form widget included in another Form")); + wi->setAutoSyncForProperty( "formName", false ); + addClass(wi); +#endif + + // inherited + wi = new KexiDataAwareWidgetInfo(this, "stdwidgets", "KLineEdit"); + wi->setPixmap("lineedit"); + wi->setClassName("KexiDBLineEdit"); + wi->addAlternateClassName("QLineEdit", true/*override*/); + wi->addAlternateClassName("KLineEdit", true/*override*/); + wi->setIncludeFileName("klineedit.h"); + wi->setName(i18n("Text Box")); + wi->setNamePrefix( + i18n("Widget name. This string will be used to name widgets of this class. " + "It must _not_ contain white spaces and non latin1 characters.", "textBox")); + wi->setDescription(i18n("A widget for entering and displaying text")); + addClass(wi); + + // inherited + wi = new KexiDataAwareWidgetInfo(this, "stdwidgets", "KTextEdit"); + wi->setPixmap("textedit"); + wi->setClassName("KexiDBTextEdit"); + wi->addAlternateClassName("QTextEdit", true/*override*/); + wi->addAlternateClassName("KTextEdit", true/*override*/); + wi->setIncludeFileName("ktextedit.h"); + wi->setName(i18n("Text Editor")); + wi->setNamePrefix( + i18n("Widget name. This string will be used to name widgets of this class. " + "It must _not_ contain white spaces and non latin1 characters.", "textEditor")); + wi->setDescription(i18n("A multiline text editor")); + addClass(wi); + + wi = new KFormDesigner::WidgetInfo( + this, "containers", "QFrame" /*we're inheriting to get i18n'd strings already translated there*/); + wi->setPixmap("frame"); + wi->setClassName("KexiFrame"); + wi->addAlternateClassName("QFrame", true/*override*/); + wi->setName(i18n("Frame")); + wi->setNamePrefix( + i18n("Widget name. This string will be used to name widgets of this class. " + "It must _not_ contain white spaces and non latin1 characters.", "frame")); + wi->setDescription(i18n("A simple frame widget")); + addClass(wi); + + wi = new KexiDataAwareWidgetInfo( + this, "stdwidgets", "QLabel" /*we're inheriting to get i18n'd strings already translated there*/); + wi->setPixmap("label"); + wi->setClassName("KexiDBLabel"); + wi->addAlternateClassName("QLabel", true/*override*/); + wi->addAlternateClassName("KexiLabel", true/*override*/); //older + wi->setName(i18n("Text Label", "Label")); + wi->setNamePrefix( + i18n("Widget name. This string will be used to name widgets of this class. " + "It must _not_ contain white spaces and non latin1 characters.", "label")); + wi->setDescription(i18n("A widget for displaying text")); + addClass(wi); + +#ifndef KEXI_NO_IMAGEBOX_WIDGET + wi = new KexiDataAwareWidgetInfo( + this, "stdwidgets", "KexiPictureLabel" /*we're inheriting to get i18n'd strings already translated there*/); + wi->setPixmap("pixmaplabel"); + wi->setClassName("KexiDBImageBox"); + wi->addAlternateClassName("KexiPictureLabel", true/*override*/); + wi->addAlternateClassName("KexiImageBox", true/*override*/); //older + wi->setName(i18n("Image Box")); + wi->setNamePrefix( + i18n("Widget name. This string will be used to name widgets of this class. " + "It must _not_ contain white spaces and non latin1 characters.", "image")); + wi->setDescription(i18n("A widget for displaying images")); +// wi->setCustomTypeForProperty("pixmapData", KexiCustomPropertyFactory::PixmapData); + wi->setCustomTypeForProperty("pixmapId", KexiCustomPropertyFactory::PixmapId); + addClass(wi); + + setInternalProperty("KexiDBImageBox", "dontStartEditingOnInserting", "1"); +// setInternalProperty("KexiDBImageBox", "forceShowAdvancedProperty:pixmap", "1"); +#endif + +#ifdef KEXI_DB_COMBOBOX_WIDGET + wi = new KexiDataAwareWidgetInfo( + this, "stdwidgets", "KComboBox" /*we're inheriting to get i18n'd strings already translated there*/); + wi->setPixmap("combo"); + wi->setClassName("KexiDBComboBox"); + wi->addAlternateClassName("KComboBox", true/*override*/); + wi->setName(i18n("Combo Box")); + wi->setNamePrefix( + i18n("Widget name. This string will be used to name widgets of this class. " + "It must _not_ contain white spaces and non latin1 characters.", "comboBox")); + wi->setDescription(i18n("A combo box widget")); + addClass(wi); +#endif + + wi = new KexiDataAwareWidgetInfo(this, "stdwidgets", "QCheckBox"); + wi->setPixmap("check"); + wi->setClassName("KexiDBCheckBox"); + wi->addAlternateClassName("QCheckBox", true/*override*/); + wi->setName(i18n("Check Box")); + wi->setNamePrefix( + i18n("Widget name. This string will be used to name widgets of this class. " + "It must _not_ contain white spaces and non latin1 characters.", "checkBox")); + wi->setDescription(i18n("A check box with text label")); + addClass(wi); + +#ifndef KEXI_NO_AUTOFIELD_WIDGET + wi = new KexiDataAwareWidgetInfo(this); + wi->setPixmap("autofield"); + wi->setClassName("KexiDBAutoField"); + wi->addAlternateClassName("KexiDBFieldEdit", true/*override*/); //older + wi->setName(i18n("Auto Field")); + wi->setNamePrefix( + i18n("Widget name. This string will be used to name widgets of this class. " + "It must _not_ contain white spaces and non latin1 characters", "autoField")); + wi->setDescription(i18n("A widget containing an automatically selected editor " + "and a label to edit the value of a database field of any type.")); + addClass(wi); +#endif + +/* +#if KDE_VERSION >= KDE_MAKE_VERSION(3,1,9) + KexiDataAwareWidgetInfo *wDate = new KexiDataAwareWidgetInfo(this, "stdwidgets", "KDateWidget"); +#else + KexiDataAwareWidgetInfo *wDate = new KexiDataAwareWidgetInfo(this, "stdwidgets", "QDateEdit"); +#endif + wDate->setPixmap("dateedit"); + wDate->setClassName("KexiDBDateEdit"); + wDate->addAlternateClassName("QDateEdit", true);//override + wDate->addAlternateClassName("KDateWidget", true);//override + wDate->setName(i18n("Date Widget")); + wDate->setNamePrefix( + i18n("Widget name. This string will be used to name widgets of this class. It must _not_ contain white spaces and non latin1 characters.", "dateWidget")); + wDate->setDescription(i18n("A widget to input and display a date")); + addClass(wDate); + +#if KDE_VERSION >= KDE_MAKE_VERSION(3,1,9) + KexiDataAwareWidgetInfo *wTime = new KexiDataAwareWidgetInfo(this, "stdwidgets", "KTimeWidget"); +#else + KexiDataAwareWidgetInfo *wTime = new KexiDataAwareWidgetInfo(this, "stdwidgets", "QTimeEdit"); +#endif + wTime->setPixmap("timeedit"); + wTime->setClassName("KexiDBTimeEdit"); + wTime->addAlternateClassName("QTimeEdit", true);//override + wTime->addAlternateClassName("KTimeWidget", true);//override + wTime->setName(i18n("Time Widget")); + wTime->setNamePrefix( + i18n("Widget name. This string will be used to name widgets of this class. It must _not_ contain white spaces and non latin1 characters.", "timeWidget")); + wTime->setDescription(i18n("A widget to input and display a time")); + addClass(wTime); + +#if KDE_VERSION >= KDE_MAKE_VERSION(3,1,9) + KexiDataAwareWidgetInfo *wDateTime = new KexiDataAwareWidgetInfo(this, "stdwidgets", "KDateTimeWidget"); +#else + KexiDataAwareWidgetInfo *wDateTime = new KexiDataAwareWidgetInfo(this, "stdwidgets", "KDateTimeWidget"); +#endif + wDateTime->setPixmap("datetimeedit"); + wDateTime->setClassName("KexiDBDateTimeEdit"); + wDateTime->addAlternateClassName("QDateTimeEdit", true);//override + wDateTime->addAlternateClassName("KDateTimeWidget", true);//override + wDateTime->setName(i18n("Date/Time Widget")); + wDateTime->setNamePrefix( + i18n("Widget name. This string will be used to name widgets of this class. It must _not_ contain white spaces and non latin1 characters.", "dateTimeWidget")); + wDateTime->setDescription(i18n("A widget to input and display a date and time")); + addClass(wDateTime); +*/ + +/* KexiDataAwareWidgetInfo *wIntSpinBox = new KexiDataAwareWidgetInfo(this, "stdwidgets", "KIntSpinBox"); + wIntSpinBox->setPixmap("spin"); + wIntSpinBox->setClassName("KexiDBIntSpinBox"); + wIntSpinBox->addAlternateClassName("QSpinBox", true); + wIntSpinBox->addAlternateClassName("KIntSpinBox", true); + wIntSpinBox->setName(i18n("Integer Number Spin Box")); + wIntSpinBox->setNamePrefix( + i18n("Widget name. This string will be used to name widgets of this class. It must _not_ contain white spaces and non latin1 characters.", "intSpinBox")); + wIntSpinBox->setDescription(i18n("A spin box widget to input and display integer numbers")); + addClass(wIntSpinBox); + + KexiDataAwareWidgetInfo *wDoubleSpinBox = new KexiDataAwareWidgetInfo(this, "stdwidgets"); + wDoubleSpinBox->setPixmap("spin"); + wDoubleSpinBox->setClassName("KexiDBDoubleSpinBox"); + wDoubleSpinBox->addAlternateClassName("KDoubleSpinBox", true); + wDoubleSpinBox->setName(i18n("Floating-point Number Spin Box")); + wDoubleSpinBox->setNamePrefix( + i18n("Widget name. This string will be used to name widgets of this class. It must _not_ contain white spaces and non latin1 characters.", "dblSpinBox")); + wDoubleSpinBox->setDescription(i18n("A spin box widget to input and display floating-point numbers")); + addClass(wDoubleSpinBox);*/ + + // inherited + wi = new KFormDesigner::WidgetInfo( + this, "stdwidgets", "KPushButton"); + wi->addAlternateClassName("KexiPushButton"); + wi->setName(i18n("Command Button")); + wi->setNamePrefix( + i18n("Widget name. This string will be used to name widgets of this class. " + "It must _not_ contain white spaces and non latin1 characters.", "button")); + wi->setDescription(i18n("A command button to execute actions")); + addClass(wi); + + m_propDesc["dataSource"] = i18n("Data Source"); + m_propDesc["formName"] = i18n("Form Name"); + m_propDesc["onClickAction"] = i18n("On Click"); + m_propDesc["onClickActionOption"] = i18n("On Click Option"); + m_propDesc["autoTabStops"] = i18n("Auto Tab Order"); + m_propDesc["shadowEnabled"] = i18n("Shadow Enabled"); + m_propDesc["on"] = i18n("On: button", "On"); + + m_propDesc["widgetType"] = i18n("Editor Type"); + //for autofield's type: inherit i18n from KexiDB + m_propValDesc["Auto"] = i18n("AutoField editor's type", "Auto"); + m_propValDesc["Text"] = KexiDB::Field::typeName(KexiDB::Field::Text); + m_propValDesc["Integer"] = KexiDB::Field::typeName(KexiDB::Field::Integer); + m_propValDesc["Double"] = KexiDB::Field::typeName(KexiDB::Field::Double); + m_propValDesc["Boolean"] = KexiDB::Field::typeName(KexiDB::Field::Boolean); + m_propValDesc["Date"] = KexiDB::Field::typeName(KexiDB::Field::Date); + m_propValDesc["Time"] = KexiDB::Field::typeName(KexiDB::Field::Time); + m_propValDesc["DateTime"] = KexiDB::Field::typeName(KexiDB::Field::DateTime); + m_propValDesc["MultiLineText"] = i18n("AutoField editor's type", "Multiline Text"); + m_propValDesc["ComboBox"] = i18n("AutoField editor's type", "Drop-Down List"); + m_propValDesc["Image"] = i18n("AutoField editor's type", "Image"); + +// m_propDesc["labelCaption"] = i18n("Label Text"); + m_propDesc["autoCaption"] = i18n("Auto Label"); + m_propDesc["foregroundLabelColor"] = i18n("Label Text Color"); + m_propDesc["backgroundLabelColor"] = i18n("(a property name, keep the text narrow!)", + "Label Background\nColor"); + + m_propDesc["labelPosition"] = i18n("Label Position"); + m_propValDesc["Left"] = i18n("Label Position", "Left"); + m_propValDesc["Top"] = i18n("Label Position", "Top"); + m_propValDesc["NoLabel"] = i18n("Label Position", "No Label"); + + m_propDesc["sizeInternal"] = i18n("Size"); +// m_propDesc["pixmap"] = i18n("Image"); + m_propDesc["pixmapId"] = i18n("Image"); + m_propDesc["scaledContents"] = i18n("Scaled Contents"); + m_propDesc["keepAspectRatio"] = i18n("Keep Aspect Ratio (short)", "Keep Ratio"); + + //hide classes that are replaced by db-aware versions + hideClass("KexiPictureLabel"); + hideClass("KComboBox"); + + //used in labels, frames... + m_propDesc["frameColor"] = i18n("Frame Color"); + m_propDesc["dropDownButtonVisible"] = + i18n("Drop-Down Button for Image Box Visible (a property name, keep the text narrow!)", + "Drop-Down\nButton Visible"); + + //for checkbox + m_propValDesc["TristateDefault"] = i18n("Tristate checkbox, default", "Default"); + m_propValDesc["TristateOn"] = i18n("Tristate checkbox, yes", "Yes"); + m_propValDesc["TristateOff"] = i18n("Tristate checkbox, no", "No"); + + //for combobox + m_propDesc["editable"] = i18n("Editable combobox", "Editable"); +} + +KexiDBFactory::~KexiDBFactory() +{ +} + +QWidget* +KexiDBFactory::createWidget(const QCString &c, QWidget *p, const char *n, + KFormDesigner::Container *container, int options) +{ + kexipluginsdbg << "KexiDBFactory::createWidget() " << this << endl; + + QWidget *w=0; + QString text( container->form()->library()->textForWidgetName(n, c) ); + const bool designMode = options & KFormDesigner::WidgetFactory::DesignViewMode; + + if(c == "KexiDBSubForm") + w = new KexiDBSubForm(container->form(), p, n); + else if(c == "KexiDBLineEdit") + { + w = new KexiDBLineEdit(p, n); + if (designMode) + w->setCursor(QCursor(Qt::ArrowCursor)); + } + else if(c == "KexiDBTextEdit") + { + w = new KexiDBTextEdit(p, n); + if (designMode) + w->setCursor(QCursor(Qt::ArrowCursor)); + } + else if(c == "QFrame" || c == "KexiFrame") + { + w = new KexiFrame(p, n); + new KFormDesigner::Container(container, w, container); + } + else if(c == "KexiDBLabel") + w = new KexiDBLabel(text, p, n); +#ifndef KEXI_NO_IMAGEBOX_WIDGET + else if(c == "KexiDBImageBox") { + w = new KexiDBImageBox(designMode, p, n); + connect(w, SIGNAL(idChanged(long)), this, SLOT(slotImageBoxIdChanged(long))); + } +#endif +#ifndef KEXI_NO_AUTOFIELD_WIDGET + else if(c == "KexiDBAutoField") + w = new KexiDBAutoField(p, n, designMode); +#endif + else if(c == "KexiDBCheckBox") + w = new KexiDBCheckBox(text, p, n); + else if(c == "KexiDBComboBox") + w = new KexiDBComboBox(p, n, designMode); +/* else if(c == "KexiDBTimeEdit") + w = new KexiDBTimeEdit(QTime::currentTime(), p, n); + else if(c == "KexiDBDateEdit") + w = new KexiDBDateEdit(QDate::currentDate(), p, n); + else if(c == "KexiDBDateTimeEdit") + w = new KexiDBDateTimeEdit(QDateTime::currentDateTime(), p, n);*/ +// else if(c == "KexiDBIntSpinBox") +// w = new KexiDBIntSpinBox(p, n); +// else if(c == "KexiDBDoubleSpinBox") +// w = new KexiDBDoubleSpinBox(p, n); + else if(c == "KPushButton" || c == "KexiPushButton") + w = new KexiPushButton(text, p, n); + + return w; +} + +bool +KexiDBFactory::createMenuActions(const QCString &classname, QWidget *w, QPopupMenu *menu, + KFormDesigner::Container *) +{ + if(classname == "QPushButton" || classname == "KPushButton" || classname == "KexiPushButton") + { +/*! @todo also call createMenuActions() for inherited factory! */ + m_assignAction->plug( menu ); + return true; + } + else if(classname == "KexiDBImageBox") + { + KexiDBImageBox *imageBox = static_cast<KexiDBImageBox*>(w); + imageBox->contextMenu()->updateActionsAvailability(); + KActionCollection *ac = imageBox->contextMenu()->actionCollection(); + KPopupMenu *subMenu = new KPopupMenu(); +//! @todo make these actions undoable/redoable + menu->insertItem(i18n("&Image"), subMenu); + ac->action("insert")->plug(subMenu); + ac->action("file_save_as")->plug(subMenu); + subMenu->insertSeparator(); + ac->action("edit_cut")->plug(subMenu); + ac->action("edit_copy")->plug(subMenu); + ac->action("edit_paste")->plug(subMenu); + ac->action("delete")->plug(subMenu); + if (ac->action("properties")) { + subMenu->insertSeparator(); + ac->action("properties")->plug(subMenu); + } + } + return false; +} + +void +KexiDBFactory::createCustomActions(KActionCollection* col) +{ + //this will create shared instance action for design mode (special collection is provided) + m_assignAction = new KAction( i18n("&Assign Action..."), SmallIconSet("form_action"), + 0, 0, 0, col, "widget_assign_action"); +} + +bool +KexiDBFactory::startEditing(const QCString &classname, QWidget *w, KFormDesigner::Container *container) +{ + m_container = container; + if(classname == "KexiDBLineEdit") + { +//! @todo this code should not be copied here but +//! just inherited StdWidgetFactory::clearWidgetContent() should be called + KLineEdit *lineedit = static_cast<KLineEdit*>(w); + createEditor(classname, lineedit->text(), lineedit, container, + lineedit->geometry(), lineedit->alignment(), true); + return true; + } + if(classname == "KexiDBTextEdit") + { +//! @todo this code should not be copied here but +//! just inherited StdWidgetFactory::clearWidgetContent() should be called + KTextEdit *textedit = static_cast<KTextEdit*>(w); + createEditor(classname, textedit->text(), textedit, container, + textedit->geometry(), textedit->alignment(), true, true); + //copy a few properties + KTextEdit *ed = dynamic_cast<KTextEdit *>( editor(w) ); + ed->setWrapPolicy(textedit->wrapPolicy()); + ed->setWordWrap(textedit->wordWrap()); + ed->setTabStopWidth(textedit->tabStopWidth()); + ed->setWrapColumnOrWidth(textedit->wrapColumnOrWidth()); + ed->setLinkUnderline(textedit->linkUnderline()); + ed->setTextFormat(textedit->textFormat()); + ed->setHScrollBarMode(textedit->hScrollBarMode()); + ed->setVScrollBarMode(textedit->vScrollBarMode()); + return true; + } + else if ( classname == "KexiDBLabel" ) { + KexiDBLabel *label = static_cast<KexiDBLabel*>(w); + m_widget = w; + if(label->textFormat() == RichText) + { + QString text = label->text(); + if ( editRichText( label, text ) ) + { + changeProperty( "textFormat", "RichText", container->form() ); + changeProperty( "text", text, container->form() ); + } + + if ( classname == "KexiDBLabel" ) + w->resize(w->sizeHint()); + } + else + { + createEditor(classname, label->text(), label, container, + label->geometry(), label->alignment(), + false, label->alignment() & Qt::WordBreak /*multiline*/); + } + return true; + } + else if (classname == "KexiDBSubForm") { + // open the form in design mode + KexiMainWindow *mainWin = KexiUtils::findParent<KexiMainWindow>(w, "KexiMainWindow"); + KexiDBSubForm *subform = static_cast<KexiDBSubForm*>(w); + if(mainWin) { + bool openingCancelled; + mainWin->openObject("kexi/form", subform->formName(), Kexi::DesignViewMode, + openingCancelled); + } + return true; + } +#if 0 + else if( (classname == "KexiDBDateEdit") || (classname == "KexiDBDateTimeEdit") || (classname == "KexiDBTimeEdit") + /*|| (classname == "KexiDBIntSpinBox") || (classname == "KexiDBDoubleSpinBox")*/ ) { + disableFilter(w, container); + return true; + } +#endif + else if(classname == "KexiDBAutoField") { + if(static_cast<KexiDBAutoField*>(w)->hasAutoCaption()) + return false; // caption is auto, abort editing + QLabel *label = static_cast<KexiDBAutoField*>(w)->label(); + createEditor(classname, label->text(), label, container, label->geometry(), label->alignment()); + return true; + } + else if (classname == "KexiDBCheckBox") { + KexiDBCheckBox *cb = static_cast<KexiDBCheckBox*>(w); + QRect r( cb->geometry() ); + r.setLeft( r.left() + 2 + cb->style().subRect( QStyle::SR_CheckBoxIndicator, cb ).width() ); + createEditor(classname, cb->text(), cb, container, r, Qt::AlignAuto); + return true; + } + else if(classname == "KexiDBImageBox") { + KexiDBImageBox *image = static_cast<KexiDBImageBox*>(w); + image->insertFromFile(); + return true; + } + return false; +} + +bool +KexiDBFactory::previewWidget(const QCString &, QWidget *, KFormDesigner::Container *) +{ + return false; +} + +bool +KexiDBFactory::clearWidgetContent(const QCString & /*classname*/, QWidget *w) +{ +//! @todo this code should not be copied here but +//! just inherited StdWidgetFactory::clearWidgetContent() should be called + KexiFormDataItemInterface *iface = dynamic_cast<KexiFormDataItemInterface*>(w); + if(iface) + iface->clear(); + return true; +} + +QValueList<QCString> +KexiDBFactory::autoSaveProperties(const QCString & /*classname*/) +{ + QValueList<QCString> lst; +// if(classname == "KexiDBSubForm") + //lst << "formName"; +// if(classname == "KexiDBLineEdit") +// lst += "dataSource"; +// if(classname == "KexiDBAutoField") +// lst << "labelCaption"; + return lst; +} + +bool +KexiDBFactory::isPropertyVisibleInternal(const QCString& classname, QWidget *w, + const QCString& property, bool isTopLevel) +{ + //general + if (property=="dataSource" || property=="dataSourceMimeType") { + return false; //force + } + + bool ok = true; + + if(classname == "KexiPushButton") { + ok = property!="isDragEnabled" +#ifdef KEXI_NO_UNFINISHED + && property!="onClickAction" /*! @todo reenable */ + && property!="onClickActionOption" /*! @todo reenable */ + && property!="iconSet" /*! @todo reenable */ + && property!="stdItem" /*! @todo reenable stdItem */ +#endif + ; + } + else if(classname == "KexiDBLineEdit") + ok = property!="urlDropsEnabled" + && property!="vAlign" +#ifdef KEXI_NO_UNFINISHED + && property!="inputMask" + && property!="maxLength" //!< we may want to integrate this with db schema +#endif + ; + else if(classname == "KexiDBComboBox") + ok = property!="autoCaption" + && property!="labelPosition" + && property!="widgetType" + && property!="fieldTypeInternal" + && property!="fieldCaptionInternal"; //hide properties that come with KexiDBAutoField + else if(classname == "KexiDBTextEdit") + ok = property!="undoDepth" + && property!="undoRedoEnabled" //always true! + && property!="dragAutoScroll" //always true! + && property!="overwriteMode" //always false! + && property!="resizePolicy" + && property!="autoFormatting" //too complex +#ifdef KEXI_NO_UNFINISHED + && property!="paper" +#endif + ; + else if(classname == "KexiDBSubForm") + ok = property!="dragAutoScroll" + && property!="resizePolicy" + && property!="focusPolicy"; + else if(classname == "KexiDBForm") + ok = property!="iconText" + && property!="geometry" /*nonsense for toplevel widget; for size, "size" property is used*/; + else if(classname == "KexiDBLabel") + ok = property!="focusPolicy"; + else if(classname == "KexiDBAutoField") { + if (!isTopLevel && property=="caption") + return true; //force + if (property=="fieldTypeInternal" || property=="fieldCaptionInternal" +//! @todo unhide in 2.0 + || property=="widgetType") + return false; + ok = property!="text"; /* "text" is not needed as "caption" is used instead */ + } + else if (classname == "KexiDBImageBox") { + ok = property!="font" && property!="wordbreak"; + } + else if(classname == "KexiDBCheckBox") { + //hide text property if the widget is a child of an autofield beause there's already "caption" for this purpose + if (property=="text" && w && dynamic_cast<KFormDesigner::WidgetWithSubpropertiesInterface*>(w->parentWidget())) + return false; + ok = property!="autoRepeat"; + } + + return ok && WidgetFactory::isPropertyVisibleInternal(classname, w, property, isTopLevel); +} + +bool +KexiDBFactory::propertySetShouldBeReloadedAfterPropertyChange(const QCString& classname, + QWidget *w, const QCString& property) +{ + Q_UNUSED(classname); + Q_UNUSED(w); + if (property=="fieldTypeInternal" || property=="widgetType") + return true; + return false; +} + +bool +KexiDBFactory::changeText(const QString &text) +{ + KFormDesigner::Form *form = m_container ? m_container->form() : 0; + if (!form) + return false; + if (!form->selectedWidget()) + return false; + QCString n( form->selectedWidget()->className() ); +// QWidget *w = WidgetFactory::widget(); + if(n == "KexiDBAutoField") { + changeProperty("caption", text, form); + return true; + } + //! \todo check field's geometry + return false; +} + +void +KexiDBFactory::resizeEditor(QWidget *editor, QWidget *w, const QCString &classname) +{ + //QSize s = widget->size(); + //QPoint p = widget->pos(); + + if(classname == "KexiDBAutoField") + editor->setGeometry( static_cast<KexiDBAutoField*>(w)->label()->geometry() ); +} + +void +KexiDBFactory::slotImageBoxIdChanged(KexiBLOBBuffer::Id_t id) +{ +//old KexiFormView *formView = KexiUtils::findParent<KexiFormView>((QWidget*)m_widget, "KexiFormView"); + + // (js) heh, porting to KFormDesigner::FormManager::self() singleton took me entire day of work... + KFormDesigner::Form *form = KFormDesigner::FormManager::self()->activeForm(); + KexiFormView *formView = form ? KexiUtils::findParent<KexiFormView>((QWidget*)form->widget(), "KexiFormView") : 0; + if (formView) { + changeProperty("pixmapId", (uint)/*! @todo unsafe */id, form); +//old formView->setUnsavedLocalBLOB(m_widget, id); + formView->setUnsavedLocalBLOB(form->selectedWidget(), id); + } +} + +KFORMDESIGNER_WIDGET_FACTORY(KexiDBFactory, kexidbwidgets) + +#include "kexidbfactory.moc" diff --git a/kexi/plugins/forms/kexidbfactory.h b/kexi/plugins/forms/kexidbfactory.h new file mode 100644 index 00000000..6064a001 --- /dev/null +++ b/kexi/plugins/forms/kexidbfactory.h @@ -0,0 +1,74 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr> + 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 KEXIDBFACTORY_H +#define KEXIDBFACTORY_H + +#include <formeditor/widgetfactory.h> + +class KAction; + +namespace KFormDesigner { + class Form; + class FormManager; +} + +//! Kexi Factory (DB widgets + subform) +class KexiDBFactory : public KFormDesigner::WidgetFactory +{ + Q_OBJECT + + public: + KexiDBFactory(QObject *parent, const char *name, const QStringList &args); + virtual ~KexiDBFactory(); + + virtual QWidget *createWidget(const QCString &classname, QWidget *parent, const char *name, + KFormDesigner::Container *container, int options = DefaultOptions ); + + virtual void createCustomActions(KActionCollection* col); + virtual bool createMenuActions(const QCString &classname, QWidget *w, QPopupMenu *menu, + KFormDesigner::Container *container); + virtual bool startEditing(const QCString &classname, QWidget *w, KFormDesigner::Container *container); + virtual bool previewWidget(const QCString &, QWidget *, KFormDesigner::Container *); + virtual bool clearWidgetContent(const QCString &classname, QWidget *w); + + //virtual void saveSpecialProperty(const QString &classname, const QString &name, const QVariant &value, QWidget *w, + //QDomElement &parentNode, QDomDocument &parent) {} + //virtual void readSpecialProperty(const QCString &classname, QDomElement &node, QWidget *w, KFormDesigner::ObjectTreeItem *item) {} + virtual QValueList<QCString> autoSaveProperties(const QCString &classname); + + protected slots: + void slotImageBoxIdChanged(long id); /*KexiBLOBBuffer::Id_t*/ + + protected: + virtual bool changeText(const QString &newText); + virtual void resizeEditor(QWidget *editor, QWidget *widget, const QCString &classname); + + virtual bool isPropertyVisibleInternal(const QCString& classname, QWidget *w, + const QCString& property, bool isTopLevel); + + //! Sometimes property sets should be reloaded when a given property value changed. + virtual bool propertySetShouldBeReloadedAfterPropertyChange(const QCString& classname, QWidget *w, + const QCString& property); + + KAction* m_assignAction; +}; + +#endif diff --git a/kexi/plugins/forms/kexidbtextwidgetinterface.cpp b/kexi/plugins/forms/kexidbtextwidgetinterface.cpp new file mode 100644 index 00000000..47eabe9d --- /dev/null +++ b/kexi/plugins/forms/kexidbtextwidgetinterface.cpp @@ -0,0 +1,71 @@ +/* This file is part of the KDE project + Copyright (C) 2005-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 "kexidbtextwidgetinterface.h" +#include "kexiformdataiteminterface.h" +#include <kexidb/queryschema.h> +#include <kexiutils/utils.h> +#include <qframe.h> +#include <qpainter.h> + +KexiDBTextWidgetInterface::KexiDBTextWidgetInterface() + : m_autonumberDisplayParameters(0) +{ +} + +KexiDBTextWidgetInterface::~KexiDBTextWidgetInterface() +{ + delete m_autonumberDisplayParameters; +} + +void KexiDBTextWidgetInterface::setColumnInfo(KexiDB::QueryColumnInfo* cinfo, QWidget *w) +{ + if (cinfo->field->isAutoIncrement()) { + if (!m_autonumberDisplayParameters) + m_autonumberDisplayParameters = new KexiDisplayUtils::DisplayParameters(); + KexiDisplayUtils::initDisplayForAutonumberSign(*m_autonumberDisplayParameters, w); + } +} + +void KexiDBTextWidgetInterface::paint( QFrame *w, QPainter* p, bool textIsEmpty, int alignment, bool hasFocus ) +{ + KexiFormDataItemInterface *dataItemIface = dynamic_cast<KexiFormDataItemInterface*>(w); + KexiDB::QueryColumnInfo *columnInfo = dataItemIface ? dataItemIface->columnInfo() : 0; + if (columnInfo && columnInfo->field && dataItemIface->cursorAtNewRow() && textIsEmpty) { + const int margin = w->lineWidth() + w->midLineWidth(); + if (columnInfo->field->isAutoIncrement() && m_autonumberDisplayParameters) { + if (w->hasFocus()) { + p->setPen( + KexiUtils::blendedColors( + m_autonumberDisplayParameters->textColor, w->palette().active().base(), 1, 3)); + } + KexiDisplayUtils::paintAutonumberSign(*m_autonumberDisplayParameters, p, + 2 + margin + w->margin(), margin, w->width() - margin*2 -2-2, + w->height() - margin*2 -2, alignment, hasFocus); + } + } +} + +void KexiDBTextWidgetInterface::event( QEvent * e, QWidget *w, bool textIsEmpty ) +{ + if (e->type()==QEvent::FocusIn || e->type()==QEvent::FocusOut) { + if (m_autonumberDisplayParameters && textIsEmpty) + w->repaint(); + } +} diff --git a/kexi/plugins/forms/kexidbtextwidgetinterface.h b/kexi/plugins/forms/kexidbtextwidgetinterface.h new file mode 100644 index 00000000..ca10fc4e --- /dev/null +++ b/kexi/plugins/forms/kexidbtextwidgetinterface.h @@ -0,0 +1,53 @@ +/* This file is part of the KDE project + Copyright (C) 2005-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 KexiDBTextWidgetInterface_H +#define KexiDBTextWidgetInterface_H + +#include <widget/utils/kexidisplayutils.h> + +namespace KexiDB { + class QueryColumnInfo; +} +class QFrame; + +//! @short An interface providing common text editor's functionality +/*! Widgets (e.g. KexiDBLineEdit, KexiDBTextEdit) implementing KexiFormDataItemInterface + use this interface to customize painting and data handling. */ +class KEXIFORMUTILS_EXPORT KexiDBTextWidgetInterface +{ + public: + KexiDBTextWidgetInterface(); + ~KexiDBTextWidgetInterface(); + + //! Called from KexiFormDataItemInterface::setColumnInfo(KexiDB::QueryColumnInfo* cinfo) implementation. + void setColumnInfo(KexiDB::QueryColumnInfo* cinfo, QWidget *w); + + //! Called from paintEvent( QPaintEvent *pe ) method of the data aware widget. + void paint( QFrame *w, QPainter *p, bool textIsEmpty, int alignment, bool hasFocus ); + + //! Called from event( QEvent * e ) method of the data aware widget. + void event( QEvent * e, QWidget *w, bool textIsEmpty ); + + protected: + //! parameters for displaying autonumber sign + KexiDisplayUtils::DisplayParameters *m_autonumberDisplayParameters; +}; + +#endif diff --git a/kexi/plugins/forms/kexiformdataiteminterface.cpp b/kexi/plugins/forms/kexiformdataiteminterface.cpp new file mode 100644 index 00000000..c87a2dab --- /dev/null +++ b/kexi/plugins/forms/kexiformdataiteminterface.cpp @@ -0,0 +1,68 @@ +/* This file is part of the KDE project + Copyright (C) 2005-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 "kexiformdataiteminterface.h" +#include "kexiformscrollview.h" +#include <kexidb/queryschema.h> +#include <kexiutils/utils.h> + +KexiFormDataItemInterface::KexiFormDataItemInterface() + : KexiDataItemInterface() + , m_columnInfo(0) + , m_displayParametersForEnteredValue(0) + , m_displayParametersForDefaultValue(0) + , m_displayDefaultValue(false) +{ +} + +KexiFormDataItemInterface::~KexiFormDataItemInterface() +{ + delete m_displayParametersForEnteredValue; + delete m_displayParametersForDefaultValue; +} + +void KexiFormDataItemInterface::undoChanges() +{ +// m_disable_signalValueChanged = true; + setValueInternal(QString::null, false); +// m_disable_signalValueChanged = false; +} + +KexiDB::Field* KexiFormDataItemInterface::field() const +{ + return m_columnInfo ? m_columnInfo->field : 0; +} + +void KexiFormDataItemInterface::setDisplayDefaultValue(QWidget* widget, bool displayDefaultValue) +{ + m_displayDefaultValue = displayDefaultValue; + if (!m_displayParametersForDefaultValue) { + m_displayParametersForEnteredValue = new KexiDisplayUtils::DisplayParameters(widget); + m_displayParametersForDefaultValue = new KexiDisplayUtils::DisplayParameters(); + KexiDisplayUtils::initDisplayForDefaultValue(*m_displayParametersForDefaultValue, widget); + } +} + +void KexiFormDataItemInterface::cancelEditor() +{ + QWidget *parentWidget = dynamic_cast<QWidget*>(this)->parentWidget(); + KexiFormScrollView* view = KexiUtils::findParent<KexiFormScrollView>(parentWidget, "KexiFormScrollView"); + if (view) + view->cancelEditor(); +} diff --git a/kexi/plugins/forms/kexiformdataiteminterface.h b/kexi/plugins/forms/kexiformdataiteminterface.h new file mode 100644 index 00000000..99d20db4 --- /dev/null +++ b/kexi/plugins/forms/kexiformdataiteminterface.h @@ -0,0 +1,145 @@ +/* This file is part of the KDE project + Copyright (C) 2005-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 KEXIFORMDATAITEMINTERFACE_H +#define KEXIFORMDATAITEMINTERFACE_H + +#include <widget/utils/kexidisplayutils.h> +#include <kexidataiteminterface.h> +#include <qwidget.h> + +namespace KexiDB { + class Field; +} + +//! An interface for declaring form widgets to be data-aware. +class KEXIFORMUTILS_EXPORT KexiFormDataItemInterface : public KexiDataItemInterface +{ + public: + KexiFormDataItemInterface(); + virtual ~KexiFormDataItemInterface(); + + //! \return the name of the data source for this widget. + //! Data source usually means here a table or query, a field name or an expression. + inline QString dataSource() const { return m_dataSource; } + + //! Sets the name of the data source for this widget. + //! Data source usually means here a table or query or field name name. + inline void setDataSource(const QString &ds) { m_dataSource = ds; } + + /*! \return the mime type of the data source for this widget. + Data source mime type means here types like "kexi/table" or "kexi/query" + in.the data source is set to object (as within form or subform) or is empty + if the data source is set to table field or query column. */ + inline QCString dataSourceMimeType() const { return m_dataSourceMimeType; } + + /*! Sets the mime type of the data source for this widget. + Data source usually means here a "kexi/table" or "kexi/query". + @see dataSourceMimeType() */ + inline void setDataSourceMimeType(const QCString &ds) { m_dataSourceMimeType = ds; } + + /*! If \a displayDefaultValue is true, the value set by KexiDataItemInterface::setValue() + is displayed in a special way. Used by KexiFormDataProvider::fillDataItems(). + \a widget is equal to 'this'. + You can reimplement this in the widget. Always call the superclass' implementation. + setDisplayDefaultValue(.., false) is called in KexiFormScrollView::valueChanged() + as a response on data change performed by user. */ + virtual void setDisplayDefaultValue(QWidget* widget, bool displayDefaultValue); + + /*! \return true if default value is displayed for this item. */ + virtual bool hasDisplayedDefaultValue() const { return m_displayDefaultValue; } + + /*! Convenience function: casts this item to a QWidget. + Can return 0 if the item is not a QWidget-derived object. */ + virtual QWidget* widget() { return dynamic_cast<QWidget*>(this); } + + /*! Sets 'invalid' state, e.g. a text editor widget should display + text \a displayText and become read only to prevent entering data, + because updating at the database backend is not available. + \a displayText is usually set to something i18n'd like "#NAME?". + Note: that even widgets that usualy do not display texts (e.g. pixmaps) + should display \a displayText too. + */ + virtual void setInvalidState( const QString& displayText ) = 0; + + /*! Changes 'read only' flag, for this widget. + Typically this flag can be passed to a widget itself, + e.g. KLineEdit::setReadOnly(bool). */ + virtual void setReadOnly( bool readOnly ) = 0; + + //! \return database column information for this item + virtual KexiDB::Field* field() const; + + //! \return database column information for this item + virtual KexiDB::QueryColumnInfo* columnInfo() const { return m_columnInfo; } + + /*! Used internally to set database column information. + Reimplement if you need to do additional actions, + e.g. set data validator based on field type. Don't forget about + calling superclass implementation. */ + virtual void setColumnInfo(KexiDB::QueryColumnInfo* cinfo) { m_columnInfo = cinfo; } + + /*! Used internally to set visible database column information. + Reimplemented in KexiDBComboBox: except for combo box, this does nothing. */ + virtual void setVisibleColumnInfo(KexiDB::QueryColumnInfo* cinfo) { Q_UNUSED(cinfo); } + + /*! \return visible database column information for this item. + Except for combo box, this is exactly the same as columnInfo(). */ + virtual KexiDB::QueryColumnInfo* visibleColumnInfo() const { return columnInfo(); } + + /*! Does nothing, because within forms, widgets are always visible. */ + virtual void hideWidget() { } + + /*! Does nothing, because within forms, widgets are always visible. */ + virtual void showWidget() { } + + /*! Undoes changes made to this item - just resets the widget to original value. + Note: This is internal method called by KexiFormScrollView::cancelEditor(). + To cancel editing of the widget's data from the widget's code, + use KexiFormDataItemInterface::cancelEditor(). + Reimplemented in KexiDBComboBox to also revert the visible value (i.e. text) to the original state. + */ + virtual void undoChanges(); + + /* Cancels editing of the widget's data. This method just looks for + the (grand)parent KexiFormScrollView object and calls + KexiFormScrollView::cancelEditor(). */ + void cancelEditor(); + + /*! @internal + Called by top-level form on key press event. + Default implementation does nothing. + Implement this if you want to handle key presses from within the editor widget item. + \return true if \a ke should be accepted by the widget item. + This method is used e.g. in KexiDBImageBox for Key_Escape to if the popup is visible, + so the key press won't be consumed to perform "cancel editing". */ + virtual bool keyPressed(QKeyEvent *ke) { Q_UNUSED(ke); return false; }; + + protected: + QString m_dataSource; + QCString m_dataSourceMimeType; + KexiDB::QueryColumnInfo* m_columnInfo; + KexiDisplayUtils::DisplayParameters *m_displayParametersForEnteredValue; //!< used in setDisplayDefaultValue() + KexiDisplayUtils::DisplayParameters *m_displayParametersForDefaultValue; //!< used in setDisplayDefaultValue() + bool m_displayDefaultValue : 1; //!< used by setDisplayDefaultValue() + + friend class KexiDBAutoField; +}; + +#endif diff --git a/kexi/plugins/forms/kexiformeventhandler.cpp b/kexi/plugins/forms/kexiformeventhandler.cpp new file mode 100644 index 00000000..01bca201 --- /dev/null +++ b/kexi/plugins/forms/kexiformeventhandler.cpp @@ -0,0 +1,188 @@ +/* This file is part of the KDE project + Copyright (C) 2005-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 "kexiformeventhandler.h" + +#include <qwidget.h> +#include <qobjectlist.h> + +#include <kdebug.h> +#include <klocale.h> + +#include <tableview/kexitableitem.h> +#include <tableview/kexitableviewdata.h> +#include <kexidb/queryschema.h> +#include <keximainwindow.h> +#include <kexidialogbase.h> +#include <kexipart.h> +#include <kexipartinfo.h> +#include <kexipartitem.h> + +KexiFormEventAction::ActionData::ActionData() +{ +} + +bool KexiFormEventAction::ActionData::isEmpty() const +{ + return string.isEmpty(); +} + +KexiPart::Info* KexiFormEventAction::ActionData::decodeString( + QString& actionType, QString& actionArg, bool& ok) const +{ + const int idx = string.find(':'); + ok = false; + if (idx==-1) + return 0; + const QString _actionType = string.left(idx); + const QString _actionArg = string.mid(idx+1); + if (_actionType.isEmpty() || _actionArg.isEmpty()) + return 0; + KexiPart::Info *info = 0; + if (_actionType!="kaction" && _actionType!="currentForm") { + info = Kexi::partManager().infoForMimeType( QString("kexi/%1").arg(_actionType) ); + if (!info) + return 0; + } + actionType = _actionType; + actionArg = _actionArg; + ok = true; + return info; +} + +//------------------------------------- + +KexiFormEventAction::KexiFormEventAction(KexiMainWindow *mainWin, QObject* parent, + const QString& actionName, const QString& objectName, const QString& actionOption) + : KAction(parent), m_mainWin(mainWin), m_actionName(actionName), m_objectName(objectName) + , m_actionOption(actionOption) +{ +} + +KexiFormEventAction::~KexiFormEventAction() +{ +} + +void KexiFormEventAction::activate() +{ + KexiProject* project = m_mainWin->project(); + if (!project) + return; + KexiPart::Part* part = Kexi::partManager().partForMimeType( + QString("kexi/%1").arg(m_actionName) ); + if (!part) + return; + KexiPart::Item* item = project->item( part->info(), m_objectName ); + if (!item) + return; + bool actionCancelled = false; + if (m_actionOption.isEmpty()) { // backward compatibility (good defaults) + if (part->info()->isExecuteSupported()) + part->execute(item, parent()); + else + m_mainWin->openObject(item, Kexi::DataViewMode, actionCancelled); + } + else { +//! @todo react on failure... + if (m_actionOption == "open") + m_mainWin->openObject(item, Kexi::DataViewMode, actionCancelled); + else if (m_actionOption == "execute") + part->execute(item, parent()); + else if (m_actionOption == "print") { + if (part->info()->isPrintingSupported()) + m_mainWin->printItem(item); + } + else if (m_actionOption == "printPreview") { + if (part->info()->isPrintingSupported()) + m_mainWin->printPreviewForItem(item); + } + else if (m_actionOption == "pageSetup") { + if (part->info()->isPrintingSupported()) + m_mainWin->showPageSetupForItem(item); + } + else if (m_actionOption == "exportToCSV" + || m_actionOption == "copyToClipboardAsCSV") + { + if (part->info()->isDataExportSupported()) + m_mainWin->executeCustomActionForObject(item, m_actionOption); + } + else if (m_actionOption == "new") + m_mainWin->newObject( part->info(), actionCancelled ); + else if (m_actionOption == "design") + m_mainWin->openObject(item, Kexi::DesignViewMode, actionCancelled); + else if (m_actionOption == "editText") + m_mainWin->openObject(item, Kexi::TextViewMode, actionCancelled); + else if (m_actionOption == "close") { + tristate res = m_mainWin->closeObject(item); + if (~res) + actionCancelled = true; + } + } +} + +//------------------------------------------ + +KexiFormEventHandler::KexiFormEventHandler() + : m_mainWidget(0) +{ +} + +KexiFormEventHandler::~KexiFormEventHandler() +{ +} + +void KexiFormEventHandler::setMainWidgetForEventHandling(KexiMainWindow *mainWin, QWidget* mainWidget) +{ + m_mainWidget = mainWidget; + if (!m_mainWidget) + return; + + //find widgets whose will work as data items +//! @todo look for other widgets too + QObjectList *l = m_mainWidget->queryList( "KexiPushButton" ); + QObjectListIt it( *l ); + QObject *obj; + for ( ; (obj = it.current()) != 0; ++it ) { + bool ok; + KexiFormEventAction::ActionData data; + data.string = obj->property("onClickAction").toString(); + data.option = obj->property("onClickActionOption").toString(); + if (data.isEmpty()) + continue; + + QString actionType, actionArg; + KexiPart::Info* partInfo = data.decodeString(actionType, actionArg, ok); + if (!ok) + continue; + if (actionType=="kaction" || actionType=="currentForm") { + KAction *action = mainWin->actionCollection()->action( actionArg.latin1() ); + if (!action) + continue; + QObject::disconnect( obj, SIGNAL(clicked()), action, SLOT(activate()) ); //safety + QObject::connect( obj, SIGNAL(clicked()), action, SLOT(activate()) ); + } + else if (partInfo) { //'open or execute' action + KexiFormEventAction* action = new KexiFormEventAction(mainWin, obj, actionType, actionArg, + data.option); + QObject::disconnect( obj, SIGNAL(clicked()), action, SLOT(activate()) ); + QObject::connect( obj, SIGNAL(clicked()), action, SLOT(activate()) ); + } + } + delete l; +} diff --git a/kexi/plugins/forms/kexiformeventhandler.h b/kexi/plugins/forms/kexiformeventhandler.h new file mode 100644 index 00000000..e92e9ff9 --- /dev/null +++ b/kexi/plugins/forms/kexiformeventhandler.h @@ -0,0 +1,101 @@ +/* This file is part of the KDE project + Copyright (C) 2005-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 KEXIFORMEVENTHANDLER_H +#define KEXIFORMEVENTHANDLER_H + +#include <qwidget.h> +#include <kaction.h> + +class KexiMainWindow; +namespace KexiPart { + class Info; +} + +//! The KexiFormEventHandler class handles events defined within Kexi Forms +/*! For now only "onClickAction" property of Push Button widget is handled: + It's possible to connect this event to predefined global action. + + Note: This interface will be extended in the future! + + @see KexiFormPart::slotAssignAction() + */ +class KEXIFORMUTILS_EXPORT KexiFormEventHandler +{ + public: + KexiFormEventHandler(); + virtual ~KexiFormEventHandler(); + + /*! Sets \a mainWidget to be a main widget for this handler. + Also find widgets having action assigned and connects them + to appropriate actions. + For now, all of them must be KexiPushButton). + \a mainWin is used to get action list. */ + void setMainWidgetForEventHandling(KexiMainWindow *mainWin, QWidget* mainWidget); + + protected: + QWidget *m_mainWidget; +}; + +//! @internal form-level action for handling "on click" actions +class KEXIFORMUTILS_EXPORT KexiFormEventAction : public KAction +{ + public: + //! A structure used in currentActionName() + class KEXIFORMUTILS_EXPORT ActionData + { + public: + ActionData(); + + /*! Decodes action string into action type/action argument parts. + Action string has to be in a form of "actiontype:actionarg" + - Action type is passed to \a actionType on success. Action type can be "kaction" + or any of the part names (see KexiPart::Info::objectName()), e.g. "table", "query", etc. + - Action argument can be an action name in case of "kaction" type or object name + in case of action of type "table", "query", etc. + \a ok is set to true on success and to false on failure. On failure no other + values are passed. + \return part info if action type is "table", "query", etc., or 0 for "kaction" type. */ + KexiPart::Info* decodeString(QString& actionType, QString& actionArg, bool& ok) const; + + //! \return true if the action is empty + bool isEmpty() const; + + QString string; //!< action string with prefix, like "kaction:edit_copy" or "table:<tableName>" + + QString option; //!< option used when name is "table/query/etc.:\<objectName\>" is set; + //!< can be set to "open", "design", "editText", etc. + //!< @see ActionToExecuteListView::showActionsForMimeType() + }; + + KexiFormEventAction(KexiMainWindow *mainWin, QObject* parent, const QString& actionName, + const QString& objectName, const QString& actionOption); + virtual ~KexiFormEventAction(); + + public slots: + //! Activates the action. If the object supports executing (macro, script), + //! it is executed; otherwise (table, query, form,...) it is opened in its data view. + virtual void activate(); + + private: + KexiMainWindow *m_mainWin; + QString m_actionName, m_objectName, m_actionOption; +}; + +#endif diff --git a/kexi/plugins/forms/kexiformhandler.desktop b/kexi/plugins/forms/kexiformhandler.desktop new file mode 100644 index 00000000..3b476daa --- /dev/null +++ b/kexi/plugins/forms/kexiformhandler.desktop @@ -0,0 +1,115 @@ +[Desktop Entry] +Type=Service +ServiceTypes=Kexi/Handler + +GenericName=Forms +GenericName[bg]=Формуляри +GenericName[br]=Paperennoù-reol +GenericName[ca]=Formularis +GenericName[cs]=Formuláře +GenericName[cy]=Ffurflenni +GenericName[da]=Formularer +GenericName[de]=Formulare +GenericName[el]=Φόρμες +GenericName[eo]=Formularoj +GenericName[es]=Formularios +GenericName[et]=Vormid +GenericName[eu]=Formularioak +GenericName[fa]=برگهها +GenericName[fi]=Lomakkeet +GenericName[fr]=Formulaires +GenericName[fy]=Formulieren +GenericName[ga]=Foirmeacha +GenericName[gl]=Formularios +GenericName[he]=טפסים +GenericName[hi]=फॉर्म्स +GenericName[hr]=Obrasci +GenericName[hu]=Űrlapok +GenericName[is]=Form +GenericName[it]=Moduli +GenericName[ja]=フォーム +GenericName[km]=សំណុំបែបបទ +GenericName[lt]=Formos +GenericName[lv]=Formas +GenericName[ms]=Borang +GenericName[nb]=Skjema +GenericName[nds]=Kiekwarken +GenericName[ne]=फारमहरू +GenericName[nn]=Skjema +GenericName[pl]=Formularze +GenericName[pt]=Formulários +GenericName[pt_BR]=Formulários +GenericName[ru]=Формы +GenericName[se]=Skovit +GenericName[sk]=Formuláre +GenericName[sl]=Obrazci +GenericName[sr]=Форме +GenericName[sr@Latn]=Forme +GenericName[sv]=Formulär +GenericName[ta]=படிவங்கள் +GenericName[tr]=Formlar +GenericName[uk]=Форми +GenericName[uz]=Shakllar +GenericName[uz@cyrillic]=Шакллар +GenericName[zh_CN]=表单 +GenericName[zh_TW]=表單 +Name=Forms +Name[bg]=Формуляри +Name[br]=Paperennoù-reol +Name[ca]=Formularis +Name[cs]=Formuláře +Name[cy]=Ffurflenni +Name[da]=Formularer +Name[de]=Formulare +Name[el]=Φόρμες +Name[eo]=Formularoj +Name[es]=Formularios +Name[et]=Vormid +Name[eu]=Formularioak +Name[fa]=برگهها +Name[fi]=Lomakkeet +Name[fr]=Formulaires +Name[fy]=Formulieren +Name[ga]=Foirmeacha +Name[gl]=Formularios +Name[he]=טפסים +Name[hi]=फ़ॉर्म +Name[hr]=Obrasci +Name[hu]=Űrlapok +Name[is]=Form +Name[it]=Moduli +Name[ja]=フォーム +Name[km]=សំណុំបែបបទ +Name[lt]=Formos +Name[lv]=Formas +Name[ms]=Borang +Name[nb]=Skjema +Name[nds]=Kiekwarken +Name[ne]=फारमहरू +Name[nn]=Skjema +Name[pl]=Formularze +Name[pt]=Formulários +Name[pt_BR]=Formulários +Name[ru]=Формы +Name[se]=Skovit +Name[sk]=Formuláre +Name[sl]=Obrazci +Name[sr]=Форме +Name[sr@Latn]=Forme +Name[sv]=Formulär +Name[ta]=Kவிதி +Name[tg]=Шаклҳо +Name[tr]=Formlar +Name[uk]=Форми +Name[uz]=Shakllar +Name[uz@cyrillic]=Шакллар +Name[zh_CN]=表单 +Name[zh_TW]=表單 +X-KDE-Library=kexihandler_form +X-KDE-ParentApp=kexi +X-Kexi-PartVersion=2 +X-Kexi-TypeName=form +X-Kexi-TypeMime=kexi/form +X-Kexi-ItemIcon=form +X-Kexi-SupportsDataExport=false +X-Kexi-SupportsPrinting=false diff --git a/kexi/plugins/forms/kexiformmanager.cpp b/kexi/plugins/forms/kexiformmanager.cpp new file mode 100644 index 00000000..6134cfc8 --- /dev/null +++ b/kexi/plugins/forms/kexiformmanager.cpp @@ -0,0 +1,235 @@ +/* This file is part of the KDE project + Copyright (C) 2005 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 "kexiformmanager.h" +#include "widgets/kexidbform.h" +#include "widgets/kexidbautofield.h" +#include "kexiformscrollview.h" +#include "kexiformview.h" +#include "kexidatasourcepage.h" + +#include <formeditor/formmanager.h> +#include <formeditor/widgetpropertyset.h> +#include <formeditor/form.h> +#include <formeditor/widgetlibrary.h> +#include <formeditor/commands.h> +#include <formeditor/objecttree.h> + +#include <koproperty/set.h> +#include <koproperty/property.h> +#include <widget/kexicustompropertyfactory.h> + +KexiFormManager::KexiFormManager(KexiPart::Part *parent, const char* name) + : KFormDesigner::FormManager(parent, + KFormDesigner::FormManager::HideEventsInPopupMenu | + KFormDesigner::FormManager::SkipFileActions | + KFormDesigner::FormManager::HideSignalSlotConnections + , name) + , m_part(parent) +{ + m_emitSelectionSignalsUpdatesPropertySet = true; + KexiCustomPropertyFactory::init(); +} + +KexiFormManager::~KexiFormManager() +{ +} + +KAction* KexiFormManager::action( const char* name ) +{ + KActionCollection *col = m_part->actionCollectionForMode(Kexi::DesignViewMode); + if (!col) + return 0; + QCString n( translateName( name ).latin1() ); + KAction *a = col->action(n); + if (a) + return a; + KexiDBForm *dbform; + if (!activeForm() || !activeForm()->designMode() + || !(dbform = dynamic_cast<KexiDBForm*>(activeForm()->formWidget()))) + return 0; + KexiFormScrollView *scrollViewWidget = dynamic_cast<KexiFormScrollView*>(dbform->dataAwareObject()); + if (!scrollViewWidget) + return 0; + KexiFormView* formViewWidget = dynamic_cast<KexiFormView*>(scrollViewWidget->parent()); + if (!formViewWidget) + return 0; + return formViewWidget->parentDialog()->mainWin()->actionCollection()->action(n); +} + +KexiFormView* KexiFormManager::activeFormViewWidget() const +{ + KexiDBForm *dbform; + if (!activeForm() || !activeForm()->designMode() + || !(dbform = dynamic_cast<KexiDBForm*>(activeForm()->formWidget()))) + return 0; + KexiFormScrollView *scrollViewWidget = dynamic_cast<KexiFormScrollView*>(dbform->dataAwareObject()); + if (!scrollViewWidget) + return 0; + return dynamic_cast<KexiFormView*>(scrollViewWidget->parent()); +} + +void KexiFormManager::enableAction( const char* name, bool enable ) +{ + KexiFormView* formViewWidget = activeFormViewWidget(); + if (!formViewWidget) + return; +// if (QString(name)=="layout_menu") +// kdDebug() << "!!!!!!!!!!! " << enable << endl; + formViewWidget->setAvailable(translateName( name ).latin1(), enable); +} + +void KexiFormManager::setFormDataSource(const QCString& mime, const QCString& name) +{ + if (!activeForm()) + return; + KexiDBForm* formWidget = dynamic_cast<KexiDBForm*>(activeForm()->widget()); + if (!formWidget) + return; + +// setPropertyValueInDesignMode(formWidget, "dataSource", name); + + QCString oldDataSourceMimeType( formWidget->dataSourceMimeType() ); + QCString oldDataSource( formWidget->dataSource().latin1() ); + if (mime!=oldDataSourceMimeType || name!=oldDataSource) { + QMap<QCString, QVariant> propValues; + propValues.insert("dataSource", name); + propValues.insert("dataSourceMimeType", mime); + KFormDesigner::CommandGroup *group + = new KFormDesigner::CommandGroup(i18n("Set Form's Data Source to \"%1\"").arg(name), propertySet()); + propertySet()->createPropertyCommandsInDesignMode(formWidget, propValues, group, true /*addToActiveForm*/); + } + +/* + if (activeForm()->selectedWidget() == formWidget) { + //active form is selected: just use properties system + KFormDesigner::WidgetPropertySet *set = propertySet(); + if (!set || !set->contains("dataSource")) + return; + (*set)["dataSource"].setValue(name); + if (set->contains("dataSourceMimeType")) + (*set)["dataSourceMimeType"].setValue(mime); + return; + } + + //active form isn't selected: change it's data source and mime type by hand + QCString oldDataSourceMimeType( formWidget->dataSourceMimeType() ); + QCString oldDataSource( formWidget->dataSource().latin1() ); + + if (mime!=oldDataSourceMimeType || name!=oldDataSource) { + formWidget->setDataSourceMimeType(mime); + formWidget->setDataSource(name); + emit dirty(activeForm(), true); + + activeForm()->addCommand( + new KFormDesigner::PropertyCommand(propertySet(), QString(formWidget->name()), + oldDataSource, name, "dataSource"), + false ); + + // If the property is changed, we add it in ObjectTreeItem modifProp + KFormDesigner::ObjectTreeItem *fromTreeItem = activeForm()->objectTree()->lookup(formWidget->name()); + fromTreeItem->addModifiedProperty("dataSourceMimeType", mime); + fromTreeItem->addModifiedProperty("dataSource", name); + }*/ +} + +void KexiFormManager::setDataSourceFieldOrExpression(const QString& string, const QString& caption, + KexiDB::Field::Type type) +{ + if (!activeForm()) + return; +// KexiFormDataItemInterface* dataWidget = dynamic_cast<KexiFormDataItemInterface*>(activeForm()->selectedWidget()); +// if (!dataWidget) +// return; + + KFormDesigner::WidgetPropertySet *set = propertySet(); + if (!set || !set->contains("dataSource")) + return; + + (*set)["dataSource"].setValue(string); + + if (set->contains("autoCaption") && (*set)["autoCaption"].value().toBool()) { + if (set->contains("fieldCaptionInternal")) + (*set)["fieldCaptionInternal"].setValue(caption); + } + if (//type!=KexiDB::Field::InvalidType && + set->contains("widgetType") && (*set)["widgetType"].value().toString()=="Auto") + { + if (set->contains("fieldTypeInternal")) + (*set)["fieldTypeInternal"].setValue(type); + } + +/* QString oldDataSource( dataWidget->dataSource() ); + if (string!=oldDataSource) { + dataWidget->setDataSource(string); + emit dirty(activeForm(), true); + + buffer + }*/ +} + +void KexiFormManager::insertAutoFields(const QString& sourceMimeType, const QString& sourceName, + const QStringList& fields) +{ + KexiFormView* formViewWidget = activeFormViewWidget(); + if (!formViewWidget || !formViewWidget->form() || !formViewWidget->form()->activeContainer()) + return; + formViewWidget->insertAutoFields(sourceMimeType, sourceName, fields, + formViewWidget->form()->activeContainer()); +} + +void KexiFormManager::slotHistoryCommandExecuted() +{ + const KFormDesigner::CommandGroup *group = dynamic_cast<const KFormDesigner::CommandGroup*>(sender()); + if (group) { + if (group->commands().count()==2) { + KexiDBForm* formWidget = dynamic_cast<KexiDBForm*>(activeForm()->widget()); + if (!formWidget) + return; + QPtrListIterator<KCommand> it(group->commands()); + const KFormDesigner::PropertyCommand* pc1 = dynamic_cast<const KFormDesigner::PropertyCommand*>(it.current()); + ++it; + const KFormDesigner::PropertyCommand* pc2 = dynamic_cast<const KFormDesigner::PropertyCommand*>(it.current()); + if (pc1 && pc2 && pc1->property()=="dataSource" && pc2->property()=="dataSourceMimeType") { + const QMap<QCString, QVariant>::const_iterator it1( pc1->oldValues().constBegin() ); + const QMap<QCString, QVariant>::const_iterator it2( pc2->oldValues().constBegin() ); + if (it1.key()==formWidget->name() && it2.key()==formWidget->name()) + static_cast<KexiFormPart*>(m_part)->dataSourcePage()->setDataSource( + formWidget->dataSourceMimeType(), formWidget->dataSource().latin1()); + } + } + } +} + +/* +bool KexiFormManager::loadFormFromDomInternal(Form *form, QWidget *container, QDomDocument &inBuf) +{ + QMap<QCString,QString> customProperties; + FormIO::loadFormFromDom(myform, container, domDoc, &customProperties); +} + +bool KexiFormManager::saveFormToStringInternal(Form *form, QString &dest, int indent) +{ + QMap<QCString,QString> customProperties; + return KFormDesigner::FormIO::saveFormToString(form, dest, indent, &customProperties); +} + +*/ + +#include "kexiformmanager.moc" diff --git a/kexi/plugins/forms/kexiformmanager.h b/kexi/plugins/forms/kexiformmanager.h new file mode 100644 index 00000000..1cc5f0c6 --- /dev/null +++ b/kexi/plugins/forms/kexiformmanager.h @@ -0,0 +1,87 @@ +/* This file is part of the KDE project + Copyright (C) 2005 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 KEXIFORMMANAGER_H +#define KEXIFORMMANAGER_H + +#include <formmanager.h> +#include <kexipart.h> + +class KCommand; +class KexiFormView; + +//! @internal +//! Used to customize KFormDesigner::FormManager behaviour. +class KEXIFORMUTILS_EXPORT KexiFormManager : public KFormDesigner::FormManager +{ + Q_OBJECT + + public: + KexiFormManager(KexiPart::Part *parent, const char* name = 0); + virtual ~KexiFormManager(); + + virtual KAction* action( const char* name ); + virtual void enableAction( const char* name, bool enable ); + + public slots: + //! Receives signal from KexiDataSourcePage about changed form's data source + void setFormDataSource(const QCString& mime, const QCString& name); + + /*! Receives signal from KexiDataSourcePage about changed widget's data source. + This is because we couldn't pass objects like KexiDB::QueryColumnInfo. + + Also sets following things in KexiDBAutoField: + - caption related to the data source + - data type related to the data source */ + void setDataSourceFieldOrExpression(const QString& string, const QString& caption, + KexiDB::Field::Type type); + + /*! Receives signal from KexiDataSourcePage and inserts autofields onto the current form. */ + void insertAutoFields(const QString& sourceMimeType, const QString& sourceName, + const QStringList& fields); + + protected slots: + void slotHistoryCommandExecuted(); + + protected: + inline QString translateName( const char* name ) const; + + private: + //! Helper: return active form's view widget or 0 if there's no active form having such widget + KexiFormView* activeFormViewWidget() const; + +// virtual bool loadFormFromDomInternal(Form *form, QWidget *container, QDomDocument &inBuf); +// virtual bool saveFormToStringInternal(Form *form, QString &dest, int indent = 0); + + KexiPart::Part* m_part; +}; + +QString KexiFormManager::translateName( const char* name ) const +{ + QString n( name ); + //translate to our name space: + if (n.startsWith("align_") || n.startsWith("adjust_") || n.startsWith("layout_") + || n=="format_raise" || n=="format_raise" || n=="taborder" | n=="break_layout") + { + n.prepend("formpart_"); + } + return n; +} + +#endif diff --git a/kexi/plugins/forms/kexiformpart.cpp b/kexi/plugins/forms/kexiformpart.cpp new file mode 100644 index 00000000..8693cb5b --- /dev/null +++ b/kexi/plugins/forms/kexiformpart.cpp @@ -0,0 +1,550 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Lucijan Busch <lucijan@kde.org> + Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr> + Copyright (C) 2005 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 <kdebug.h> +#include <kgenericfactory.h> +#include <kdialogbase.h> +#include <klistview.h> +#include <ktabwidget.h> +#include <kiconloader.h> +#include <kcombobox.h> +#include <kapplication.h> +#include <kconfig.h> + +#include <kexiviewbase.h> +#include <keximainwindow.h> +#include <kexiproject.h> +#include <kexipartitem.h> +#include <kexidialogbase.h> +#include <kexidatasourcecombobox.h> +#include <kexidb/connection.h> +#include <kexidb/fieldlist.h> +#include <kexidb/field.h> +#include <kexiutils/utils.h> + +#include <form.h> +#include <formIO.h> +#include <widgetpropertyset.h> +#include <widgetlibrary.h> +#include <objecttreeview.h> +#include <koproperty/property.h> + +#include "kexiformview.h" +#include "widgets/kexidbform.h" +#include "kexiformscrollview.h" +#include "kexiactionselectiondialog.h" +#include "kexiformmanager.h" +#include "kexiformpart.h" +#include "kexidatasourcepage.h" + +//! @todo #define KEXI_SHOW_SPLITTER_WIDGET + +KFormDesigner::WidgetLibrary* KexiFormPart::static_formsLibrary = 0L; + +//! @internal +class KexiFormPart::Private +{ + public: + Private() + { + } + ~Private() + { + delete static_cast<KFormDesigner::ObjectTreeView*>(objectTreeView); + delete static_cast<KexiDataSourcePage*>(dataSourcePage); + } +// QGuardedPtr<KFormDesigner::FormManager> manager; + QGuardedPtr<KFormDesigner::ObjectTreeView> objectTreeView; + QGuardedPtr<KexiDataSourcePage> dataSourcePage; + KexiDataSourceComboBox *dataSourceCombo; +}; + +KexiFormPart::KexiFormPart(QObject *parent, const char *name, const QStringList &l) + : KexiPart::Part(parent, name, l) + , d(new Private()) +{ + // REGISTERED ID: + m_registeredPartID = (int)KexiPart::FormObjectType; + + kexipluginsdbg << "KexiFormPart::KexiFormPart()" << endl; + 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.", + "form"); + m_names["instanceCaption"] = i18n("Form"); + m_supportedViewModes = Kexi::DataViewMode | Kexi::DesignViewMode; + m_newObjectsAreDirty = true; + + // Only create form manager if it's not yet created. + // KexiReportPart could have created it already. + KFormDesigner::FormManager *formManager = KFormDesigner::FormManager::self(); + if (!formManager) + formManager = new KexiFormManager(this, "kexi_form_and_report_manager"); + + // Create and store a handle to forms' library. Reports will have their own library too. +/* @todo add configuration for supported factory groups */ + QStringList supportedFactoryGroups; + supportedFactoryGroups += "kexi"; + static_formsLibrary = KFormDesigner::FormManager::createWidgetLibrary( + formManager, supportedFactoryGroups); + static_formsLibrary->setAdvancedPropertiesVisible(false); + connect(static_formsLibrary, SIGNAL(widgetCreated(QWidget*)), + this, SLOT(slotWidgetCreatedByFormsLibrary(QWidget*))); + + connect(KFormDesigner::FormManager::self()->propertySet(), SIGNAL(widgetPropertyChanged(QWidget *, const QCString &, const QVariant&)), + this, SLOT(slotPropertyChanged(QWidget *, const QCString &, const QVariant&))); + connect(KFormDesigner::FormManager::self(), SIGNAL(autoTabStopsSet(KFormDesigner::Form*,bool)), + this, SLOT(slotAutoTabStopsSet(KFormDesigner::Form*,bool))); +} + +KexiFormPart::~KexiFormPart() +{ + delete d; +} + +KFormDesigner::WidgetLibrary* KexiFormPart::library() +{ + return static_formsLibrary; +} + +#if 0 +void KexiFormPart::initPartActions(KActionCollection *collection) +{ +//this is automatic? -no +//create child guicilent: guiClient()->setXMLFile("kexidatatableui.rc"); + + kexipluginsdbg<<"FormPart INIT ACTIONS***********************************************************************"<<endl; + //TODO + + //guiClient()->setXMLFile("kexiformui.rc"); +//js m_manager->createActions(collection, 0); +} + +void KexiFormPart::initInstanceActions( int mode, KActionCollection *col ) +{ + if (mode==Kexi::DesignViewMode) { + KFormDesigner::FormManager::self()->createActions(col, 0); + new KAction(i18n("Edit Tab Order..."), "tab_order", KShortcut(0), KFormDesigner::FormManager::self(), SLOT(editTabOrder()), col, "taborder"); + new KAction(i18n("Adjust Size"), "viewmagfit", KShortcut(0), KFormDesigner::FormManager::self(), SLOT(ajustWidgetSize()), col, "adjust"); + } + //TODO +} +#endif + +void KexiFormPart::initPartActions() +{ +// new KAction(i18n("Show Form UI Code"), "show_form_ui", CTRL+Key_U, m_manager, SLOT(showFormUICode()), +// guiClient()->actionCollection(), "show_form_ui"); +} + +void KexiFormPart::initInstanceActions() +{ +#ifdef KEXI_DEBUG_GUI + kapp->config()->setGroup("General"); + if (kapp->config()->readBoolEntry("showInternalDebugger", false)) { + new KAction(i18n("Show Form UI Code"), "compfile", + CTRL+Key_U, KFormDesigner::FormManager::self(), SLOT(showFormUICode()), + actionCollectionForMode(Kexi::DesignViewMode), "show_form_ui"); + } +#endif + + KActionCollection *col = actionCollectionForMode(Kexi::DesignViewMode); + KFormDesigner::FormManager::self()->createActions( library(), col, (KXMLGUIClient*)col->parentGUIClient() ); //guiClient() ); + + //connect actions provided by widget factories + connect( col->action("widget_assign_action"), SIGNAL(activated()), this, SLOT(slotAssignAction())); + + createSharedAction(Kexi::DesignViewMode, i18n("Clear Widget Contents"), "editclear", 0, "formpart_clear_contents"); + createSharedAction(Kexi::DesignViewMode, i18n("Edit Tab Order..."), "tab_order", 0, "formpart_taborder"); +//TODO createSharedAction(Kexi::DesignViewMode, i18n("Edit Pixmap Collection"), "icons", 0, "formpart_pixmap_collection"); +//TODO createSharedAction(Kexi::DesignViewMode, i18n("Edit Form Connections"), "connections", 0, "formpart_connections"); + +// KFormDesigner::CreateLayoutCommand + + KAction *action = createSharedAction(Kexi::DesignViewMode, i18n("Layout Widgets"), "", 0, "formpart_layout_menu", "KActionMenu"); + KActionMenu *menu = static_cast<KActionMenu*>(action); + + menu->insert( createSharedAction(Kexi::DesignViewMode, i18n("&Horizontally"), + QString::null, 0, "formpart_layout_hbox")); + menu->insert( createSharedAction(Kexi::DesignViewMode, i18n("&Vertically"), + QString::null, 0, "formpart_layout_vbox")); + menu->insert( createSharedAction(Kexi::DesignViewMode, i18n("In &Grid"), + QString::null, 0, "formpart_layout_grid")); +#ifdef KEXI_SHOW_SPLITTER_WIDGET + menu->insert( createSharedAction(Kexi::DesignViewMode, i18n("Horizontally in &Splitter"), + QString::null, 0, "formpart_layout_hsplitter")); + menu->insert( createSharedAction(Kexi::DesignViewMode, i18n("Verti&cally in Splitter"), + QString::null, 0, "formpart_layout_vsplitter")); +#endif + + createSharedAction(Kexi::DesignViewMode, i18n("&Break Layout"), QString::null, 0, "formpart_break_layout"); +/* + createSharedAction(Kexi::DesignViewMode, i18n("Lay Out Widgets &Horizontally"), QString::null, 0, "formpart_layout_hbox"); + createSharedAction(Kexi::DesignViewMode, i18n("Lay Out Widgets &Vertically"), QString::null, 0, "formpart_layout_vbox"); + createSharedAction(Kexi::DesignViewMode, i18n("Lay Out Widgets in &Grid"), QString::null, 0, "formpart_layout_grid"); +*/ + createSharedAction(Kexi::DesignViewMode, i18n("Bring Widget to Front"), "raise", 0, "formpart_format_raise"); + createSharedAction(Kexi::DesignViewMode, i18n("Send Widget to Back"), "lower", 0, "formpart_format_lower"); + +#ifndef KEXI_NO_UNFINISHED + action = createSharedAction(Kexi::DesignViewMode, i18n("Other Widgets"), "", 0, "other_widgets_menu", "KActionMenu"); +#endif + + action = createSharedAction(Kexi::DesignViewMode, i18n("Align Widgets Position"), "aoleft", 0, "formpart_align_menu", "KActionMenu"); + menu = static_cast<KActionMenu*>(action); + menu->insert( createSharedAction(Kexi::DesignViewMode, i18n("To Left"), "aoleft", 0, "formpart_align_to_left") ); + menu->insert( createSharedAction(Kexi::DesignViewMode, i18n("To Right"), "aoright", 0, "formpart_align_to_right") ); + menu->insert( createSharedAction(Kexi::DesignViewMode, i18n("To Top"), "aotop", 0, "formpart_align_to_top") ); + menu->insert( createSharedAction(Kexi::DesignViewMode, i18n("To Bottom"), "aobottom", 0, "formpart_align_to_bottom") ); + menu->insert( createSharedAction(Kexi::DesignViewMode, i18n("To Grid"), "aopos2grid", 0, "formpart_align_to_grid") ); + + action = createSharedAction(Kexi::DesignViewMode, i18n("Adjust Widgets Size"), "aogrid", 0, "formpart_adjust_size_menu", "KActionMenu"); + menu = static_cast<KActionMenu*>(action); + menu->insert( createSharedAction(Kexi::DesignViewMode, i18n("To Fit"), "aofit", 0, "formpart_adjust_to_fit") ); + menu->insert( createSharedAction(Kexi::DesignViewMode, i18n("To Grid"), "aogrid", 0, "formpart_adjust_size_grid") ); + menu->insert( createSharedAction(Kexi::DesignViewMode, i18n("To Shortest"), "aoshortest", 0, "formpart_adjust_height_small") ); + menu->insert( createSharedAction(Kexi::DesignViewMode, i18n("To Tallest"), "aotallest", 0, "formpart_adjust_height_big") ); + menu->insert( createSharedAction(Kexi::DesignViewMode, i18n("To Narrowest"), "aonarrowest", 0, "formpart_adjust_width_small") ); + menu->insert( createSharedAction(Kexi::DesignViewMode, i18n("To Widest"), "aowidest", 0, "formpart_adjust_width_big") ); +} + +KexiDialogTempData* +KexiFormPart::createTempData(KexiDialogBase* dialog) +{ + return new KexiFormPart::TempData(dialog); +} + +KexiViewBase* KexiFormPart::createView(QWidget *parent, KexiDialogBase* dialog, + KexiPart::Item &item, int viewMode, QMap<QString,QString>*) +{ + Q_UNUSED( viewMode ); + + kexipluginsdbg << "KexiFormPart::createView()" << endl; + KexiMainWindow *win = dialog->mainWin(); + if (!win || !win->project() || !win->project()->dbConnection()) + return 0; + + KexiFormView *view = new KexiFormView(win, parent, item.name().latin1(), + win->project()->dbConnection() ); + + return view; +} + +void +KexiFormPart::generateForm(KexiDB::FieldList *list, QDomDocument &domDoc) +{ + //this form generates a .ui from FieldList list + //basically that is a Label and a LineEdit for each field + domDoc = QDomDocument("UI"); + QDomElement uiElement = domDoc.createElement("UI"); + domDoc.appendChild(uiElement); + uiElement.setAttribute("version", "3.1"); + uiElement.setAttribute("stdsetdef", 1); + + QDomElement baseClass = domDoc.createElement("class"); + uiElement.appendChild(baseClass); + QDomText baseClassV = domDoc.createTextNode("QWidget"); + baseClass.appendChild(baseClassV); + QDomElement baseWidget = domDoc.createElement("widget"); + baseWidget.setAttribute("class", "QWidget"); + + int y=0; + + for(unsigned int i=0; i < list->fieldCount(); i++) + { + QDomElement lclass = domDoc.createElement("widget"); + baseWidget.appendChild(lclass); + lclass.setAttribute("class", "QLabel"); + QDomElement lNameProperty = domDoc.createElement("property"); + lNameProperty.setAttribute("name", "name"); + QDomElement lType = domDoc.createElement("cstring"); + QDomText lClassN = domDoc.createTextNode(QString("l%1").arg(list->field(i)->name())); + lType.appendChild(lClassN); + lNameProperty.appendChild(lType); + lclass.appendChild(lNameProperty); + + QDomElement gNameProperty = domDoc.createElement("property"); + gNameProperty.setAttribute("name", "geometry"); + QDomElement lGType = domDoc.createElement("rect"); + + QDomElement lx = domDoc.createElement("x"); + QDomText lxV = domDoc.createTextNode("10"); + lx.appendChild(lxV); + QDomElement ly = domDoc.createElement("y"); + QDomText lyV = domDoc.createTextNode(QString::number(y + 10)); + ly.appendChild(lyV); + QDomElement lWidth = domDoc.createElement("width"); + QDomText lWidthV = domDoc.createTextNode("100"); + lWidth.appendChild(lWidthV); + QDomElement lHeight = domDoc.createElement("height"); + QDomText lHeightV = domDoc.createTextNode("20"); + lHeight.appendChild(lHeightV); + + lGType.appendChild(lx); + lGType.appendChild(ly); + lGType.appendChild(lWidth); + lGType.appendChild(lHeight); + + gNameProperty.appendChild(lGType); + lclass.appendChild(gNameProperty); + + QDomElement tNameProperty = domDoc.createElement("property"); + tNameProperty.setAttribute("name", "text"); + QDomElement lTType = domDoc.createElement("string"); + QDomText lTextV = domDoc.createTextNode(list->field(i)->name()); + lTType.appendChild(lTextV); + tNameProperty.appendChild(lTType); + lclass.appendChild(tNameProperty); + + + ///line edit! + + + QDomElement vclass = domDoc.createElement("widget"); + baseWidget.appendChild(vclass); + vclass.setAttribute("class", "KLineEdit"); + QDomElement vNameProperty = domDoc.createElement("property"); + vNameProperty.setAttribute("name", "name"); + QDomElement vType = domDoc.createElement("cstring"); + QDomText vClassN = domDoc.createTextNode(list->field(i)->name()); + vType.appendChild(vClassN); + vNameProperty.appendChild(vType); + vclass.appendChild(vNameProperty); + + QDomElement vgNameProperty = domDoc.createElement("property"); + vgNameProperty.setAttribute("name", "geometry"); + QDomElement vGType = domDoc.createElement("rect"); + + QDomElement vx = domDoc.createElement("x"); + QDomText vxV = domDoc.createTextNode("110"); + vx.appendChild(vxV); + QDomElement vy = domDoc.createElement("y"); + QDomText vyV = domDoc.createTextNode(QString::number(y + 10)); + vy.appendChild(vyV); + QDomElement vWidth = domDoc.createElement("width"); + QDomText vWidthV = domDoc.createTextNode("200"); + vWidth.appendChild(vWidthV); + QDomElement vHeight = domDoc.createElement("height"); + QDomText vHeightV = domDoc.createTextNode("20"); + vHeight.appendChild(vHeightV); + + vGType.appendChild(vx); + vGType.appendChild(vy); + vGType.appendChild(vWidth); + vGType.appendChild(vHeight); + + vgNameProperty.appendChild(vGType); + vclass.appendChild(vgNameProperty); + + y += 20; + } + + QDomElement lNameProperty = domDoc.createElement("property"); + lNameProperty.setAttribute("name", "name"); + QDomElement lType = domDoc.createElement("cstring"); + QDomText lClassN = domDoc.createTextNode("DBForm"); + lType.appendChild(lClassN); + lNameProperty.appendChild(lType); + baseWidget.appendChild(lNameProperty); + + QDomElement wNameProperty = domDoc.createElement("property"); + wNameProperty.setAttribute("name", "geometry"); + QDomElement wGType = domDoc.createElement("rect"); + + QDomElement wx = domDoc.createElement("x"); + QDomText wxV = domDoc.createTextNode("0"); + wx.appendChild(wxV); + QDomElement wy = domDoc.createElement("y"); + QDomText wyV = domDoc.createTextNode("0"); + wy.appendChild(wyV); + QDomElement wWidth = domDoc.createElement("width"); + QDomText wWidthV = domDoc.createTextNode("340"); + wWidth.appendChild(wWidthV); + QDomElement wHeight = domDoc.createElement("height"); + QDomText wHeightV = domDoc.createTextNode(QString::number(y + 30)); + wHeight.appendChild(wHeightV); + + wGType.appendChild(wx); + wGType.appendChild(wy); + wGType.appendChild(wWidth); + wGType.appendChild(wHeight); + + wNameProperty.appendChild(wGType); + baseWidget.appendChild(wNameProperty); + + uiElement.appendChild(baseWidget); +} + +void KexiFormPart::slotAutoTabStopsSet(KFormDesigner::Form *form, bool set) +{ + Q_UNUSED( form ); + + KoProperty::Property &p = (*KFormDesigner::FormManager::self()->propertySet())["autoTabStops"]; + if (!p.isNull()) + p.setValue(QVariant(set, 4)); +} + +void KexiFormPart::slotAssignAction() +{ + KexiDBForm *dbform; + if (!KFormDesigner::FormManager::self()->activeForm() || !KFormDesigner::FormManager::self()->activeForm()->designMode() + || !(dbform = dynamic_cast<KexiDBForm*>(KFormDesigner::FormManager::self()->activeForm()->formWidget()))) + return; + + KFormDesigner::WidgetPropertySet * propSet = KFormDesigner::FormManager::self()->propertySet(); + + KoProperty::Property &onClickActionProp = propSet->property("onClickAction"); + if (onClickActionProp.isNull()) + return; + KoProperty::Property &onClickActionOptionProp = propSet->property("onClickActionOption"); + KexiFormEventAction::ActionData data; + data.string = onClickActionProp.value().toString(); + if (!onClickActionOptionProp.isNull()) + data.option = onClickActionOptionProp.value().toString(); + + KexiFormScrollView *scrollViewWidget = dynamic_cast<KexiFormScrollView*>(dbform->dataAwareObject()); + if (!scrollViewWidget) + return; + KexiFormView* formViewWidget = dynamic_cast<KexiFormView*>(scrollViewWidget->parent()); + if (!formViewWidget) + return; + + KexiMainWindow * mainWin = formViewWidget->parentDialog()->mainWin(); + KexiActionSelectionDialog dlg(mainWin, dbform, data, + propSet->property("name").value().toCString()); + + if(dlg.exec() == QDialog::Accepted) { + data = dlg.currentAction(); + //update property value + propSet->property("onClickAction").setValue(data.string); + propSet->property("onClickActionOption").setValue(data.option); + } +} + +QString +KexiFormPart::i18nMessage(const QCString& englishMessage, KexiDialogBase* dlg) const +{ + Q_UNUSED(dlg); + if (englishMessage=="Design of object \"%1\" has been modified.") + return i18n("Design of form \"%1\" has been modified."); + if (englishMessage=="Object \"%1\" already exists.") + return i18n("Form \"%1\" already exists."); + + return englishMessage; +} + +void +KexiFormPart::slotPropertyChanged(QWidget *w, const QCString &name, const QVariant &value) +{ + Q_UNUSED( w ); + + if (!KFormDesigner::FormManager::self()->activeForm()) + return; + if (name == "autoTabStops") { + //QWidget *w = KFormDesigner::FormManager::self()->activeForm()->selectedWidget(); + //update autoTabStops setting at KFD::Form level + KFormDesigner::FormManager::self()->activeForm()->setAutoTabStops( value.toBool() ); + } + if (KFormDesigner::FormManager::self()->activeForm()->widget() && name == "geometry") { + //fall back to sizeInternal property.... + if (KFormDesigner::FormManager::self()->propertySet()->contains("sizeInternal")) + KFormDesigner::FormManager::self()->propertySet()->property("sizeInternal").setValue(value.toRect().size()); + } +} + +/*KFormDesigner::FormManager* +KexiFormPart::manager() const +{ + return d->manager; +}*/ + +KexiDataSourcePage* KexiFormPart::dataSourcePage() const +{ + return d->dataSourcePage; +} + +void KexiFormPart::setupCustomPropertyPanelTabs(KTabWidget *tab, KexiMainWindow* mainWin) +{ + if (!d->objectTreeView) { + d->objectTreeView = new KFormDesigner::ObjectTreeView(0, "KexiFormPart:ObjectTreeView"); + KFormDesigner::FormManager::self()->setObjectTreeView(d->objectTreeView); //important: assign to manager + d->dataSourcePage = new KexiDataSourcePage(0, "dataSourcePage"); + connect(d->dataSourcePage, SIGNAL(jumpToObjectRequested(const QCString&, const QCString&)), + mainWin, SLOT(highlightObject(const QCString&, const QCString&))); + connect(d->dataSourcePage, SIGNAL(formDataSourceChanged(const QCString&, const QCString&)), + KFormDesigner::FormManager::self(), SLOT(setFormDataSource(const QCString&, const QCString&))); + connect(d->dataSourcePage, SIGNAL(dataSourceFieldOrExpressionChanged(const QString&, const QString&, KexiDB::Field::Type)), + KFormDesigner::FormManager::self(), SLOT(setDataSourceFieldOrExpression(const QString&, const QString&, KexiDB::Field::Type))); + connect(d->dataSourcePage, SIGNAL(insertAutoFields(const QString&, const QString&, const QStringList&)), + KFormDesigner::FormManager::self(), SLOT(insertAutoFields(const QString&, const QString&, const QStringList&))); + } + + KexiProject *prj = mainWin->project(); + d->dataSourcePage->setProject(prj); + + tab->addTab( d->dataSourcePage, SmallIconSet("database"), ""); + tab->setTabToolTip( d->dataSourcePage, i18n("Data Source")); + + tab->addTab( d->objectTreeView, SmallIconSet("widgets"), ""); + tab->setTabToolTip( d->objectTreeView, i18n("Widgets")); +} + +void KexiFormPart::slotWidgetCreatedByFormsLibrary(QWidget* widget) +{ + QStrList signalNames(widget->metaObject()->signalNames()); + if (!signalNames.isEmpty()) { + const char *handleDragMoveEventSignal = "handleDragMoveEvent(QDragMoveEvent*)"; + const char *handleDropEventSignal = "handleDropEvent(QDropEvent*)"; + + for (QStrListIterator it(signalNames); it.current(); ++it) { + if (0==qstrcmp(it.current(), handleDragMoveEventSignal)) { + kdDebug() << it.current() << endl; + KexiFormView *formView = KexiUtils::findParent<KexiFormView>(widget, "KexiFormView"); + if (formView) { + connect(widget, SIGNAL(handleDragMoveEvent(QDragMoveEvent*)), + formView, SLOT(slotHandleDragMoveEvent(QDragMoveEvent*))); + } + } + else if (0==qstrcmp(it.current(), handleDropEventSignal)) { + kdDebug() << it.current() << endl; + KexiFormView *formView = KexiUtils::findParent<KexiFormView>(widget, "KexiFormView"); + if (formView) { + connect(widget, SIGNAL(handleDropEvent(QDropEvent*)), + formView, SLOT(slotHandleDropEvent(QDropEvent*))); + } + } + } + } +} + +//---------------- + +KexiFormPart::TempData::TempData(QObject* parent) + : KexiDialogTempData(parent) +{ +} + +KexiFormPart::TempData::~TempData() +{ +} + +#include "kexiformpart.moc" diff --git a/kexi/plugins/forms/kexiformpart.h b/kexi/plugins/forms/kexiformpart.h new file mode 100644 index 00000000..1ddbab53 --- /dev/null +++ b/kexi/plugins/forms/kexiformpart.h @@ -0,0 +1,108 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Lucijan Busch <lucijan@kde.org> + Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr> + Copyright (C) 2005 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 KEXIFORMPART_H +#define KEXIFORMPART_H + +#include <qdom.h> +#include <qcstring.h> + +#include <kexi.h> +#include <kexipart.h> +#include <kexidialogbase.h> +#include <kexiblobbuffer.h> + +namespace KFormDesigner +{ + class WidgetLibrary; + class FormManager; + class Form; +} + +namespace KexiDB +{ + class FieldList; +} + +class KexiDataSourcePage; + +//! Kexi Form Plugin +/*! It just creates a \ref KexiFormView. See there for most of code. */ +class KEXIFORMUTILS_EXPORT KexiFormPart : public KexiPart::Part +{ + Q_OBJECT + + public: + KexiFormPart(QObject *parent, const char *name, const QStringList &); + virtual ~KexiFormPart(); + + //! \return a pointer to Forms Widget Library. + static KFormDesigner::WidgetLibrary* library(); + + KexiDataSourcePage* dataSourcePage() const; + + void generateForm(KexiDB::FieldList *list, QDomDocument &domDoc); + + class TempData : public KexiDialogTempData + { + public: + TempData(QObject* parent); + ~TempData(); + QGuardedPtr<KFormDesigner::Form> form; + QGuardedPtr<KFormDesigner::Form> previewForm; + QString tempForm; + QPoint scrollViewContentsPos; //!< to preserve contents pos after switching to other view + int resizeMode; //!< form's window's resize mode -one of KexiFormView::ResizeMode items + //! Used in KexiFormView::setUnsavedLocalBLOBs() + QMap<QWidget*, KexiBLOBBuffer::Id_t> unsavedLocalBLOBs; + //! Used when loading a form from (temporary) XML in Data View + //! to get unsaved blobs collected at design mode. + QMap<QCString, KexiBLOBBuffer::Id_t> unsavedLocalBLOBsByName; + }; + + virtual QString i18nMessage(const QCString& englishMessage, + KexiDialogBase* dlg) const; + + 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(); + virtual void initInstanceActions(); + virtual void setupCustomPropertyPanelTabs(KTabWidget *tab, KexiMainWindow* mainWin); + + static KFormDesigner::WidgetLibrary* static_formsLibrary; + + protected slots: + void slotAutoTabStopsSet(KFormDesigner::Form *form, bool set); + void slotAssignAction(); + void slotPropertyChanged(QWidget *widget, const QCString &name, const QVariant &value); + void slotWidgetCreatedByFormsLibrary(QWidget* widget); + + private: + class Private; + Private* d; +}; + +#endif + diff --git a/kexi/plugins/forms/kexiformpartinstui.rc b/kexi/plugins/forms/kexiformpartinstui.rc new file mode 100644 index 00000000..75f233f2 --- /dev/null +++ b/kexi/plugins/forms/kexiformpartinstui.rc @@ -0,0 +1,77 @@ +<!DOCTYPE kpartgui> +<kpartgui name="kexiformpartinst" version="13"> + +<MenuBar> + <Menu name="edit"> + <Action name="fompart_clear_contents"/> + <Separator /> + <Action name="formpart_taborder"/> + <Action name="formpart_adjust_size"/> + <Action name="formpart_pixmap_collection"/> + <Action name="formpart_connections"/> + <!-- Action name="change_style"/ --> + </Menu> + <Menu name="format" noMerge="0"> + <text>&Format</text> + <Action name="snap_to_grid"/> + <Separator/> + <Action name="formpart_layout_menu"/> + <Action name="formpart_break_layout"/> + <Separator/> + <Action name="formpart_align_menu"/> + <Action name="formpart_adjust_size_menu"/> + <Separator/> + <Action name="formpart_format_raise"/> + <Action name="formpart_format_lower"/> + </Menu> +</MenuBar> + +<ToolBar name="widgets" fullWidth="false"> + <text>Widgets</text> + <Action name="pointer"/> + <!-- Action name="drag_connection"/ --> + <Separator/> + <Action name="library_widget_KexiDBAutoField"/> + <Action name="library_widget_KexiDBLabel"/> + <Action name="library_widget_KexiPictureLabel"/> + <Action name="library_widget_KexiDBImageBox"/> + <Action name="library_widget_KexiDBLineEdit"/> + <Action name="library_widget_KexiDBTextEdit"/> + <Action name="library_widget_KPushButton"/> + <Action name="library_widget_KexiDBComboBox"/> + <!-- Action name="library_widget_QRadioButton"/ --> + <Action name="library_widget_KexiDBCheckBox"/> + <Action name="library_widget_Spacer"/> + <Action name="library_widget_Line"/> + <Separator/> + <Action name="library_widget_KexiFrame"/> + <Action name="library_widget_QGroupBox"/> + <Action name="library_widget_KFDTabWidget"/> + <!-- TODO Action name="library_widget_KexiDBSubForm"/ --> + <Separator/> + <Action name="library_widget_Spring"/> + <Separator/> + <Action name="other_widgets_menu"/> + <ActionList name="library_widgets" /> + <Merge/> +</ToolBar> +<ToolBar name="format" fullWidth="false" noMerge="1"> +<text>Format</text> + <!-- Action name="formpart_layout_menu"/ --> + <Action name="formpart_align_menu"/> + <Action name="formpart_adjust_size_menu"/> + <Action name="show_form_ui" /> +</ToolBar> +<!-- ToolBar name="tools" fullWidth="false"> + <Action name="change_style"/> +</ToolBar --> + +<Menu name="other_widgets_menu"> + <Action name="library_widget_KexiDBIntSpinBox"/> + <Action name="library_widget_KexiDBDoubleSpinBox"/> + <Action name="library_widget_KexiDBDateEdit"/> + <Action name="library_widget_KexiDBTimeEdit"/> + <Action name="library_widget_KexiDBDateTimeEdit"/> +</Menu> + +</kpartgui> diff --git a/kexi/plugins/forms/kexiformpartui.rc b/kexi/plugins/forms/kexiformpartui.rc new file mode 100644 index 00000000..20fd49d8 --- /dev/null +++ b/kexi/plugins/forms/kexiformpartui.rc @@ -0,0 +1,10 @@ +<!DOCTYPE kpartgui> +<kpartgui name="kexiformpart" version="6"> + +<!-- ToolBar name="design" fullWidth="false" noMerge="0"> + <text>Design</text> + <Action name="show_form_ui"/> +</ToolBar --> + +</kpartgui> + diff --git a/kexi/plugins/forms/kexiforms.cpp b/kexi/plugins/forms/kexiforms.cpp new file mode 100644 index 00000000..07c4726f --- /dev/null +++ b/kexi/plugins/forms/kexiforms.cpp @@ -0,0 +1,25 @@ +/* This file is part of the KDE project + Copyright (C) 2005 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 <kgenericfactory.h> + +#include "kexiformpart.h" + +K_EXPORT_COMPONENT_FACTORY( kexihandler_form, KGenericFactory<KexiFormPart>("kexihandler_form") ) + diff --git a/kexi/plugins/forms/kexiformscrollview.cpp b/kexi/plugins/forms/kexiformscrollview.cpp new file mode 100644 index 00000000..351a1e3e --- /dev/null +++ b/kexi/plugins/forms/kexiformscrollview.cpp @@ -0,0 +1,587 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr> + 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 "kexiformscrollview.h" +//#include "kexiformview.h" + +#include <formeditor/form.h> +#include <formeditor/formmanager.h> +#include <formeditor/objecttree.h> +#include <formeditor/commands.h> +#include <widget/utils/kexirecordmarker.h> + +#include <kpopupmenu.h> +#include <kdebug.h> + +KexiFormScrollView::KexiFormScrollView(QWidget *parent, bool preview) + : KexiScrollView(parent, preview) + , KexiRecordNavigatorHandler() + , KexiSharedActionClient() + , KexiDataAwareObjectInterface() + , KexiFormDataProvider() + , KexiFormEventHandler() +{ + m_currentLocalSortColumn = -1; /* no column */ + m_localSortingOrder = -1; /* no sorting */ + m_previousItem = 0; + m_navPanel = m_scrollViewNavPanel; //copy this pointer from KexiScrollView + if (preview) { + setRecordNavigatorVisible(true); +//tmp +// recordNavigator()->setEditingIndicatorEnabled(true); +// recordNavigator()->showEditingIndicator(true); + } + + connect(this, SIGNAL(resizingStarted()), this, SLOT(slotResizingStarted())); + + m_popupMenu = new KPopupMenu(this, "contextMenu"); + +// setFocusPolicy(NoFocus); +} + +KexiFormScrollView::~KexiFormScrollView() +{ + if (m_owner) + delete m_data; + m_data = 0; +} + +void +KexiFormScrollView::show() +{ + KexiScrollView::show(); + +#if 0 //moved to KexiFormView, OK? + //now get resize mode settings for entire form + if (m_preview) { + KexiFormView* fv = dynamic_cast<KexiFormView*>(parent()); + int resizeMode = fv ? fv->resizeMode() : KexiFormView::ResizeAuto; + if (resizeMode == KexiFormView::ResizeAuto) + setResizePolicy(AutoOneFit); + } +#endif +} + +void +KexiFormScrollView::slotResizingStarted() +{ + if(m_form && KFormDesigner::FormManager::self()) + setSnapToGrid(KFormDesigner::FormManager::self()->snapWidgetsToGrid(), m_form->gridSize()); + else + setSnapToGrid(false); +} + +int KexiFormScrollView::rowsPerPage() const +{ + //! @todo + return 10; +} + +void KexiFormScrollView::selectCellInternal() +{ + //m_currentItem is already set by KexiDataAwareObjectInterface::setCursorPosition() + if (m_currentItem) { + if (m_currentItem!=m_previousItem) { + fillDataItems(*m_currentItem, cursorAtNewRow()); + m_previousItem = m_currentItem; + } + } + else { + m_previousItem = 0; + } +} + +void KexiFormScrollView::ensureCellVisible(int row, int col/*=-1*/) +{ + Q_UNUSED( row ); + Q_UNUSED( col ); + //! @todo +// if (m_currentItem) + //fillDataItems(*m_currentItem); + +// if (m_form->tabStops()->first() && m_form->tabStops()->first()->widget()) +// m_form->tabStops()->first()->widget()->setFocus(); +} + +void KexiFormScrollView::moveToRecordRequested(uint r) +{ + //! @todo + selectRow(r); +} + +void KexiFormScrollView::moveToLastRecordRequested() +{ + //! @todo + selectLastRow(); +} + +void KexiFormScrollView::moveToPreviousRecordRequested() +{ + //! @todo + selectPrevRow(); +} + +void KexiFormScrollView::moveToNextRecordRequested() +{ + //! @todo + selectNextRow(); +} + +void KexiFormScrollView::moveToFirstRecordRequested() +{ + //! @todo + selectFirstRow(); +} + +void KexiFormScrollView::clearColumnsInternal(bool repaint) +{ + Q_UNUSED( repaint ); + //! @todo +} + +void KexiFormScrollView::addHeaderColumn(const QString& caption, const QString& description, + const QIconSet& icon, int width) +{ + Q_UNUSED( caption ); + Q_UNUSED( description ); + Q_UNUSED( icon ); + Q_UNUSED( width ); + + //! @todo +} + +int KexiFormScrollView::currentLocalSortingOrder() const +{ + //! @todo + return m_localSortingOrder; +} + +int KexiFormScrollView::currentLocalSortColumn() const +{ + return m_currentLocalSortColumn; +} + +void KexiFormScrollView::setLocalSortingOrder(int col, int order) +{ + //! @todo + m_currentLocalSortColumn = col; + m_localSortingOrder = order; +} + +void KexiFormScrollView::sortColumnInternal(int col, int order) +{ + Q_UNUSED( col ); + Q_UNUSED( order ); + //! @todo +} + +void KexiFormScrollView::updateGUIAfterSorting() +{ + //! @todo +} + +void KexiFormScrollView::createEditor(int row, int col, const QString& addText, + bool removeOld) +{ + Q_UNUSED( row ); + Q_UNUSED( addText ); + Q_UNUSED( removeOld ); + + if (isReadOnly()) { + kexipluginsdbg << "KexiFormScrollView::createEditor(): DATA IS READ ONLY!"<<endl; + return; + } + if (column( col )->isReadOnly()) { + kexipluginsdbg << "KexiFormScrollView::createEditor(): COL IS READ ONLY!"<<endl; + return; + } + + //! @todo + const bool startRowEdit = !m_rowEditing; //remember if we're starting row edit + + if (!m_rowEditing) { + //we're starting row editing session + m_data->clearRowEditBuffer(); + + m_rowEditing = true; + //indicate on the vheader that we are editing: + if (m_verticalHeader) + m_verticalHeader->setEditRow(m_curRow); + if (isInsertingEnabled() && m_currentItem==m_insertItem) { + //we should know that we are in state "new row editing" + m_newRowEditing = true; + //'insert' row editing: show another row after that: + m_data->append( m_insertItem ); + //new empty insert item + m_insertItem = m_data->createItem(); //new KexiTableItem(dataColumns()); +// updateContents(); + if (m_verticalHeader) + m_verticalHeader->addLabel(); +// m_verticalHeaderAlreadyAdded = true; + updateWidgetContentsSize(); + //refr. current and next row +// updateContents(columnPos(0), rowPos(row), viewport()->width(), d->rowHeight*2); +//js: warning this breaks behaviour (cursor is skipping, etc.): qApp->processEvents(500); +// ensureVisible(columnPos(m_curCol), rowPos(row+1)+d->rowHeight-1, columnWidth(m_curCol), d->rowHeight); + +// m_verticalHeader->setOffset(contentsY()); + } + } + + m_editor = editor(col); //m_dataItems.at(col); + if (!m_editor) + return; + + if (startRowEdit) { + recordNavigator()->showEditingIndicator(true); +// recordNavigator()->updateButtons(); //refresh 'next btn' + + emit rowEditStarted(m_curRow); + } +} + +KexiDataItemInterface *KexiFormScrollView::editor( int col, bool ignoreMissingEditor ) +{ + Q_UNUSED( ignoreMissingEditor ); + + if (!m_data || col<0 || col>=columns()) + return 0; + + return dynamic_cast<KexiFormDataItemInterface*>(dbFormWidget()->orderedDataAwareWidgets()->at( col )); +// KexiFormDataItemInterface *item = m_dataItems.at(col); + //return item; + +/* + KexiTableViewColumn *tvcol = m_data->column(col); +// int t = tvcol->field->type(); + + //find the editor for this column + KexiDataItemInterface *editor = d->editors[ tvcol ]; + if (editor) + return editor; + + //not found: create +// editor = KexiCellEditorFactory::createEditor(*m_data->column(col)->field, this); + editor = KexiCellEditorFactory::createEditor(*m_data->column(col), this); + if (!editor) {//create error! + if (!ignoreMissingEditor) { + //js TODO: show error??? + cancelRowEdit(); + } + return 0; + } + editor->hide(); + connect(editor,SIGNAL(editRequested()),this,SLOT(slotEditRequested())); + connect(editor,SIGNAL(cancelRequested()),this,SLOT(cancelEditor())); + connect(editor,SIGNAL(acceptRequested()),this,SLOT(acceptEditor())); + + editor->resize(columnWidth(col)-1, rowHeight()-1); + editor->installEventFilter(this); + if (editor->widget()) + editor->widget()->installEventFilter(this); + //store + d->editors.insert( tvcol, editor ); + return editor;*/ +} + +void KexiFormScrollView::editorShowFocus( int row, int col ) +{ + Q_UNUSED( row ); + Q_UNUSED( col ); + //! @todo +// if (m_currentItem) +// m_provider->fillDataItems(*m_currentItem); +} + +void KexiFormScrollView::updateCell(int row, int col) +{ + Q_UNUSED( row ); + Q_UNUSED( col ); + //! @todo +} + +void KexiFormScrollView::updateCurrentCell() +{ +} + +void KexiFormScrollView::updateRow(int row) +{ + Q_UNUSED(row) + //! @todo +} + +void KexiFormScrollView::updateWidgetContents() +{ + //! @todo +} + +void KexiFormScrollView::updateWidgetContentsSize() +{ + //! @todo +} + +void KexiFormScrollView::updateWidgetScrollBars() +{ + //! @todo +} + +void KexiFormScrollView::slotRowRepaintRequested(KexiTableItem& item) +{ + Q_UNUSED( item ); + //! @todo +} + +/*void KexiFormScrollView::slotAboutToDeleteRow(KexiTableItem& item, + KexiDB::ResultInfo* result, bool repaint) +{ + //! @todo +}*/ + +/*void KexiFormScrollView::slotRowDeleted() +{ + //! @todo +}*/ + +void KexiFormScrollView::slotRowInserted(KexiTableItem *item, bool repaint) +{ + Q_UNUSED( item ); + Q_UNUSED( repaint ); + //! @todo +} + +void KexiFormScrollView::slotRowInserted(KexiTableItem *item, uint row, bool repaint) +{ + Q_UNUSED( item ); + Q_UNUSED( row ); + Q_UNUSED( repaint ); + //! @todo +} + +void KexiFormScrollView::slotRowsDeleted( const QValueList<int> & ) +{ + //! @todo +} + +KexiDBForm* KexiFormScrollView::dbFormWidget() const +{ + return dynamic_cast<KexiDBForm*>(m_widget); +} + +int KexiFormScrollView::columns() const +{ + return dbFormWidget()->orderedDataAwareWidgets()->count(); //m_dataItems.count(); +} + +/*uint KexiFormScrollView::fieldNumberForColumn(int col) +{ + KexiFormDataItemInterface *item = dynamic_cast<KexiFormDataItemInterface*>(dbFormWidget()->orderedDataAwareWidgets()->at( col )); + if (!item) + return -1; + KexiFormDataItemInterfaceToIntMap::ConstIterator it(m_fieldNumbersForDataItems.find( item )); + return it!=m_fieldNumbersForDataItems.constEnd() ? it.data() : -1; +}*/ + +bool KexiFormScrollView::columnEditable(int col) +{ + kexipluginsdbg << "KexiFormScrollView::columnEditable(" << col << ")" << endl; + foreach_list (QPtrListIterator<KexiFormDataItemInterface>, it, m_dataItems) { + kexipluginsdbg << (dynamic_cast<QWidget*>(it.current()) ? dynamic_cast<QWidget*>(it.current())->name() : "" ) + << " " << it.current()->dataSource() << endl; + } + kexipluginsdbg << "-- focus widgets --" << endl; + foreach_list (QPtrListIterator<QWidget>, it, *dbFormWidget()->orderedFocusWidgets()) { + kexipluginsdbg << it.current()->name() << endl; + } + kexipluginsdbg << "-- data-aware widgets --" << endl; + foreach_list (QPtrListIterator<QWidget>, it, *dbFormWidget()->orderedDataAwareWidgets()) { + kexipluginsdbg << it.current()->name() << endl; + } + + //int index = dbFormWidget()->indexForDataItem( item ); +// KexiFormDataItemInterface *item1 = dynamic_cast<KexiFormDataItemInterface*>(dbFormWidget()->orderedFocusWidgets()->at( col )); + KexiFormDataItemInterface *item = dynamic_cast<KexiFormDataItemInterface*>(dbFormWidget()->orderedDataAwareWidgets()->at( col )); + + if (!item || item->isReadOnly()) + return false; + +// KexiFormDataItemInterfaceToIntMap::ConstIterator it(m_fieldNumbersForDataItems.find( item )); +// return KexiDataAwareObjectInterface::columnEditable( it!=m_fieldNumbersForDataItems.constEnd() ? it.data() : -1 ); + return KexiDataAwareObjectInterface::columnEditable( col ); +} + +void KexiFormScrollView::valueChanged(KexiDataItemInterface* item) +{ + if (!item) + return; + //only signal start editing when no row editing was started already + kexipluginsdbg << "** KexiFormScrollView::valueChanged(): editedItem=" + << (dbFormWidget()->editedItem ? dbFormWidget()->editedItem->value().toString() : QString::null) + << ", " + << (item ? item->value().toString() : QString::null) + << endl; + if (dbFormWidget()->editedItem!=item) { + kexipluginsdbg << "**>>> dbFormWidget()->editedItem = dynamic_cast<KexiFormDataItemInterface*>(item)" << endl; + dbFormWidget()->editedItem = dynamic_cast<KexiFormDataItemInterface*>(item); + startEditCurrentCell(); + } + fillDuplicatedDataItems(dynamic_cast<KexiFormDataItemInterface*>(item), item->value()); + + //value changed: clear 'default value' mode (e.g. a blue italic text) + dynamic_cast<KexiFormDataItemInterface*>(item)->setDisplayDefaultValue(dynamic_cast<QWidget*>(item), false); +} + +bool KexiFormScrollView::cursorAtNewRow() const +{ + return isInsertingEnabled() && ( m_currentItem==m_insertItem || m_newRowEditing ); +} + +void KexiFormScrollView::initDataContents() +{ + KexiDataAwareObjectInterface::initDataContents(); + + if (m_preview) { +//! @todo here we can react if user wanted to show the navigator + setRecordNavigatorVisible(m_data); + recordNavigator()->setEnabled(m_data); + if (m_data) { + recordNavigator()->setEditingIndicatorEnabled( !isReadOnly() ); + recordNavigator()->showEditingIndicator(false); + } + + dbFormWidget()->updateReadOnlyFlags(); + } +} + +KexiTableViewColumn* KexiFormScrollView::column(int col) +{ + const int id = fieldNumberForColumn(col); + return (id >= 0) ? m_data->column( id ) : 0; +} + +bool KexiFormScrollView::shouldDisplayDefaultValueForItem(KexiFormDataItemInterface* itemIface) const +{ + return cursorAtNewRow() + && !itemIface->columnInfo()->field->defaultValue().isNull() +//?? && (m_editor ? m_editor->value()==itemIface->columnInfo()->field->defaultValue() : true) + && !itemIface->columnInfo()->field->isAutoIncrement(); // default value defined +} + +bool KexiFormScrollView::cancelEditor() +{ + if (!dynamic_cast<KexiFormDataItemInterface*>(m_editor)) + return false; + + if (m_errorMessagePopup) + m_errorMessagePopup->close(); + + KexiFormDataItemInterface *itemIface = dynamic_cast<KexiFormDataItemInterface*>(m_editor); + itemIface->undoChanges(); + + const bool displayDefaultValue = shouldDisplayDefaultValueForItem(itemIface); + // now disable/enable "display default value" if needed (do it after setValue(), before setValue() turns it off) + if (itemIface->hasDisplayedDefaultValue() != displayDefaultValue) + itemIface->setDisplayDefaultValue( dynamic_cast<QWidget*>(itemIface), displayDefaultValue ); + + fillDuplicatedDataItems(itemIface, m_editor->value()); + + // this will clear editor pointer and close message popup (if present) + return KexiDataAwareObjectInterface::cancelEditor(); +} + +void KexiFormScrollView::updateAfterCancelRowEdit() +{ + for (QPtrListIterator<KexiFormDataItemInterface> it(m_dataItems); it.current(); ++it) { + if (dynamic_cast<QWidget*>(it.current())) { + kexipluginsdbg << "KexiFormScrollView::updateAfterCancelRowEdit(): " + << dynamic_cast<QWidget*>(it.current())->className() << " " + << dynamic_cast<QWidget*>(it.current())->name() << endl; + } + KexiFormDataItemInterface *itemIface = it.current(); + const bool displayDefaultValue = shouldDisplayDefaultValueForItem(itemIface); + itemIface->undoChanges(); + if (itemIface->hasDisplayedDefaultValue() != displayDefaultValue) + itemIface->setDisplayDefaultValue( dynamic_cast<QWidget*>(itemIface), displayDefaultValue ); + } + recordNavigator()->showEditingIndicator(false); + dbFormWidget()->editedItem = 0; +} + +void KexiFormScrollView::updateAfterAcceptRowEdit() +{ + if (!m_currentItem) + return; + recordNavigator()->showEditingIndicator(false); + dbFormWidget()->editedItem = 0; + //update visible data because there could be auto-filled (eg. autonumber) fields + fillDataItems(*m_currentItem, cursorAtNewRow()); + m_previousItem = m_currentItem; +} + +void KexiFormScrollView::beforeSwitchView() +{ + m_editor = 0; +} + +void KexiFormScrollView::refreshContentsSize() +{ + KexiScrollView::refreshContentsSize(); + //only clear cmd history when KexiScrollView::refreshContentsSizeLater() has been called + if (!m_preview && sender()==&m_delayedResize) { + if (m_form) + m_form->clearCommandHistory(); + } +} + +void KexiFormScrollView::handleDataWidgetAction(const QString& actionName) +{ + QWidget *w = focusWidget(); + KexiFormDataItemInterface *item = 0; + while (w) { + item = dynamic_cast<KexiFormDataItemInterface*>(w); + if (item) + break; + w = w->parentWidget(); + } + if (item) + item->handleAction(actionName); +} + +void KexiFormScrollView::copySelection() +{ + handleDataWidgetAction("edit_copy"); +} + +void KexiFormScrollView::cutSelection() +{ + handleDataWidgetAction("edit_cut"); +} + +void KexiFormScrollView::paste() +{ + handleDataWidgetAction("edit_paste"); +} + +int KexiFormScrollView::lastVisibleRow() const +{ +//! @todo unimplemented for now, this will be used for continuous forms + return -1; +} + +#include "kexiformscrollview.moc" diff --git a/kexi/plugins/forms/kexiformscrollview.h b/kexi/plugins/forms/kexiformscrollview.h new file mode 100644 index 00000000..12315761 --- /dev/null +++ b/kexi/plugins/forms/kexiformscrollview.h @@ -0,0 +1,297 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr> + 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 KEXIFORMSCROLLVIEW_H +#define KEXIFORMSCROLLVIEW_H + +#include "kexidataprovider.h" +#include "kexiformeventhandler.h" +#include "widgets/kexidbform.h" +#include <widget/kexiscrollview.h> +#include <widget/utils/kexirecordnavigator.h> +#include <widget/utils/kexisharedactionclient.h> +#include <widget/tableview/kexidataawareobjectiface.h> + +//! @short KexiFormScrollView class provides a widget for displaying data in a form view +/*! This class also implements: + - record navigation handling (KexiRecordNavigatorHandler) + - shared actions handling (KexiSharedActionClient) + - data-aware behaviour (KexiDataAwareObjectInterface) + - data provider bound to data-aware widgets (KexiFormDataProvider) + + @see KexiTableView +*/ +class KEXIFORMUTILS_EXPORT KexiFormScrollView : + public KexiScrollView, + public KexiRecordNavigatorHandler, + public KexiSharedActionClient, + public KexiDataAwareObjectInterface, + public KexiFormDataProvider, + public KexiFormEventHandler +{ + Q_OBJECT + KEXI_DATAAWAREOBJECTINTERFACE + + public: + KexiFormScrollView(QWidget *parent, bool preview); + virtual ~KexiFormScrollView(); + + void setForm(KFormDesigner::Form *form) { m_form = form; } + + /*! Reimplemented from KexiDataAwareObjectInterface + for checking 'readOnly' flag from a widget + ('readOnly' flag from data member is still checked though). */ + virtual bool columnEditable(int col); + + /*! \return number of visible columns in this view. + There can be a number of duplicated columns defined, + so columns() can return greater or smaller number than dataColumns(). */ + virtual int columns() const; + + /*! \return column information for column number \a col. + Reimplemented for KexiDataAwareObjectInterface: + column data corresponding to widget number is used here + (see fieldNumberForColumn()). */ + virtual KexiTableViewColumn* column(int col); + + /*! \return field number within data model connected to a data-aware + widget at column \a col. */ + virtual int fieldNumberForColumn(int col) { + KexiFormDataItemInterface *item = dynamic_cast<KexiFormDataItemInterface*>( + dbFormWidget()->orderedDataAwareWidgets()->at( col )); + if (!item) + return -1; + KexiFormDataItemInterfaceToIntMap::ConstIterator it(m_fieldNumbersForDataItems.find( item )); + return it!=m_fieldNumbersForDataItems.constEnd() ? (int)it.data() : -1; + } + + /*! @internal Used by KexiFormView in view switching. */ + void beforeSwitchView(); + + /*! \return last row visible on the screen (counting from 0). + The returned value is guaranteed to be smaller or equal to currentRow() or -1 + if there are no rows. + Implemented for KexiDataAwareObjectInterface. */ +//! @todo unimplemented for now, this will be used for continuous forms + virtual int lastVisibleRow() const; + + /*! \return vertical scrollbar. Implemented for KexiDataAwareObjectInterface. */ + virtual QScrollBar* verticalScrollBar() const { return KexiScrollView::verticalScrollBar(); } + + public slots: + /*! Reimplemented to update resize policy. */ + virtual void show(); + + //virtual void setFocus(); + + //! Implementation for KexiDataAwareObjectInterface + //! \return arbitraty value of 10. + virtual int rowsPerPage() const; + + //! Implementation for KexiDataAwareObjectInterface + virtual void ensureCellVisible(int row, int col/*=-1*/); + + virtual void moveToRecordRequested(uint r); + virtual void moveToLastRecordRequested(); + virtual void moveToPreviousRecordRequested(); + virtual void moveToNextRecordRequested(); + virtual void moveToFirstRecordRequested(); + virtual void addNewRecordRequested() { KexiDataAwareObjectInterface::addNewRecordRequested(); } + + /*! Cancels changes made to the currently active editor. + Reverts the editor's value to old one. + \return true on success or false on failure (e.g. when editor does not exist) */ + virtual bool cancelEditor(); + + public slots: + /*! Reimplemented to also clear command history right after final resize. */ + virtual void refreshContentsSize(); + + /*! Handles verticalScrollBar()'s valueChanged(int) signal. + Called when vscrollbar's value has been changed. */ +//! @todo unused for now, will be used for continuous forms + virtual void vScrollBarValueChanged(int v) { KexiDataAwareObjectInterface::vScrollBarValueChanged(v); } + + /*! Handles sliderReleased() signal of the verticalScrollBar(). Used to hide the "row number" tooltip. */ +//! @todo unused for now, will be used for continuous forms + virtual void vScrollBarSliderReleased() { KexiDataAwareObjectInterface::vScrollBarSliderReleased(); } + + /*! Handles timeout() signal of the m_scrollBarTipTimer. If the tooltip is visible, + m_scrollBarTipTimerCnt is set to 0 and m_scrollBarTipTimerCnt is restarted; + else the m_scrollBarTipTimerCnt is just set to 0.*/ +//! @todo unused for now, will be used for continuous forms + virtual void scrollBarTipTimeout() { KexiDataAwareObjectInterface::scrollBarTipTimeout(); } + + signals: + virtual void itemChanged(KexiTableItem *, int row, int col); + virtual void itemChanged(KexiTableItem *, int row, int col, QVariant oldValue); + virtual void itemDeleteRequest(KexiTableItem *, int row, int col); + virtual void currentItemDeleteRequest(); + virtual void newItemAppendedForAfterDeletingInSpreadSheetMode(); //!< does nothing + virtual void dataRefreshed(); + virtual void dataSet( KexiTableViewData *data ); + virtual void itemSelected(KexiTableItem *); + virtual void cellSelected(int col, int row); + virtual void sortedColumnChanged(int col); + virtual void rowEditStarted(int row); + virtual void rowEditTerminated(int row); + virtual void reloadActions(); + + protected slots: + void slotResizingStarted(); + + //! Handles KexiTableViewData::rowRepaintRequested() signal + virtual void slotRowRepaintRequested(KexiTableItem& item); + + //! Handles KexiTableViewData::aboutToDeleteRow() signal. Prepares info for slotRowDeleted(). + virtual void slotAboutToDeleteRow(KexiTableItem& item, KexiDB::ResultInfo* result, bool repaint) + { KexiDataAwareObjectInterface::slotAboutToDeleteRow(item, result, repaint); } + + //! Handles KexiTableViewData::rowDeleted() signal to repaint when needed. + virtual void slotRowDeleted() { KexiDataAwareObjectInterface::slotRowDeleted(); } + + //! Handles KexiTableViewData::rowInserted() signal to repaint when needed. + virtual void slotRowInserted(KexiTableItem *item, bool repaint); + + //! Like above, not db-aware version + virtual void slotRowInserted(KexiTableItem *item, uint row, bool repaint); + + virtual void slotRowsDeleted( const QValueList<int> & ); + + virtual void slotDataDestroying() { KexiDataAwareObjectInterface::slotDataDestroying(); } + + /*! Reloads data for this widget. + Handles KexiTableViewData::reloadRequested() signal. */ + virtual void reloadData() { KexiDataAwareObjectInterface::reloadData(); } + + //! Copy current selection to a clipboard (e.g. cell) + virtual void copySelection(); + + //! Cut current selection to a clipboard (e.g. cell) + virtual void cutSelection(); + + //! Paste current clipboard contents (e.g. to a cell) + virtual void paste(); + + protected: + //! Implementation for KexiDataAwareObjectInterface + virtual void clearColumnsInternal(bool repaint); + + //! Implementation for KexiDataAwareObjectInterface + virtual void addHeaderColumn(const QString& caption, const QString& description, + const QIconSet& icon, int width); + + //! Implementation for KexiDataAwareObjectInterface + virtual int currentLocalSortingOrder() const; + + //! Implementation for KexiDataAwareObjectInterface + virtual int currentLocalSortColumn() const; + + //! Implementation for KexiDataAwareObjectInterface + virtual void setLocalSortingOrder(int col, int order); + + //! Implementation for KexiDataAwareObjectInterface + void sortColumnInternal(int col, int order = 0); + + //! Implementation for KexiDataAwareObjectInterface + virtual void updateGUIAfterSorting(); + + //! Implementation for KexiDataAwareObjectInterface + virtual void createEditor(int row, int col, const QString& addText = QString::null, + bool removeOld = false); + + //! Implementation for KexiDataAwareObjectInterface + virtual KexiDataItemInterface *editor( int col, bool ignoreMissingEditor = false ); + + //! Implementation for KexiDataAwareObjectInterface + virtual void editorShowFocus( int row, int col ); + + /*! Implementation for KexiDataAwareObjectInterface + Redraws specified cell. */ + virtual void updateCell(int row, int col); + + /*! Redraws the current cell. Implemented after KexiDataAwareObjectInterface. */ + virtual void updateCurrentCell(); + + /*! Implementation for KexiDataAwareObjectInterface + Redraws all cells of specified row. */ + virtual void updateRow(int row); + + /*! Implementation for KexiDataAwareObjectInterface + Updates contents of the widget. Just call update() here on your widget. */ + virtual void updateWidgetContents(); + + /*! Implementation for KexiDataAwareObjectInterface + Implementation for KexiDataAwareObjectInterface + Updates widget's contents size e.g. using QScrollView::resizeContents(). */ + virtual void updateWidgetContentsSize(); + + /*! Implementation for KexiDataAwareObjectInterface + Updates scrollbars of the widget. + QScrollView::updateScrollbars() will be usually called here. */ + virtual void updateWidgetScrollBars(); + + KexiDBForm* dbFormWidget() const; + + //! Reimplemented from KexiFormDataProvider. Reaction for change of \a item. + virtual void valueChanged(KexiDataItemInterface* item); + + /*! Reimplemented from KexiFormDataProvider. + \return information whether we're currently at new row or now. + This can be used e.g. by data-aware widgets to determine if "(autonumber)" + label should be displayed. */ + virtual bool cursorAtNewRow() const; + + //! Implementation for KexiDataAwareObjectInterface + //! Called by KexiDataAwareObjectInterface::setCursorPosition() + //! if cursor's position is really changed. + inline virtual void selectCellInternal(); + + /*! Reimplementation: used to refresh "editing indicator" visibility. */ + virtual void initDataContents(); + + /*! @internal + Updates row appearance after canceling row edit. + Reimplemented from KexiDataAwareObjectInterface: just undoes changes for every data item. + Used by cancelRowEdit(). */ + virtual void updateAfterCancelRowEdit(); + + /*! @internal + Updates row appearance after accepting row edit. + Reimplemented from KexiDataAwareObjectInterface: just clears 'edit' indicator. + Used by cancelRowEdit(). */ + virtual void updateAfterAcceptRowEdit(); + + /*! @internal + Used to invoke copy/paste/cut etc. actions at the focused widget's level. */ + void handleDataWidgetAction(const QString& actionName); + + /*! @internal */ + bool shouldDisplayDefaultValueForItem(KexiFormDataItemInterface* itemIface) const; + + //virtual bool focusNextPrevChild( bool next ); + + KFormDesigner::Form *m_form; + int m_currentLocalSortColumn, m_localSortingOrder; + //! Used in selectCellInternal() to avoid fetching the same record twice + KexiTableItem *m_previousItem; +}; + +#endif diff --git a/kexi/plugins/forms/kexiformview.cpp b/kexi/plugins/forms/kexiformview.cpp new file mode 100644 index 00000000..7e52e5b6 --- /dev/null +++ b/kexi/plugins/forms/kexiformview.cpp @@ -0,0 +1,1278 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr> + Copyright (C) 2004-2007 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 "kexiformview.h" + +#include <qobjectlist.h> +#include <qfileinfo.h> + +#include <formeditor/form.h> +#include <formeditor/formIO.h> +#include <formeditor/formmanager.h> +#include <formeditor/objecttree.h> +#include <formeditor/container.h> +#include <formeditor/widgetpropertyset.h> +#include <formeditor/commands.h> +#include <formeditor/widgetwithsubpropertiesinterface.h> +#include <formeditor/objecttree.h> + +#include <kexi.h> +#include <kexidialogbase.h> +#include <kexidragobjects.h> +#include <kexidb/field.h> +#include <kexidb/fieldlist.h> +#include <kexidb/connection.h> +#include <kexidb/cursor.h> +#include <kexidb/utils.h> +#include <kexidb/preparedstatement.h> +#include <tableview/kexitableitem.h> +#include <tableview/kexitableviewdata.h> +#include <widget/kexipropertyeditorview.h> +#include <widget/kexiqueryparameters.h> +#include <kexiutils/utils.h> + +#include <koproperty/set.h> +#include <koproperty/property.h> + +#include "widgets/kexidbform.h" +#include "kexiformscrollview.h" +#include "kexidatasourcepage.h" +#include "widgets/kexidbautofield.h" + +#define NO_DSWIZARD + +//! @todo #define KEXI_SHOW_SPLITTER_WIDGET + +KexiFormView::KexiFormView(KexiMainWindow *mainWin, QWidget *parent, + const char *name, bool /*dbAware*/) + : KexiDataAwareView( mainWin, parent, name ) + , m_propertySet(0) + , m_resizeMode(KexiFormView::ResizeDefault) + , m_query(0) + , m_queryIsOwned(false) + , m_cursor(0) +// , m_firstFocusWidget(0) +{ + m_delayedFormContentsResizeOnShow = 0; + + QHBoxLayout *l = new QHBoxLayout(this); + l->setAutoAdd(true); + + m_scrollView = new KexiFormScrollView(this, viewMode()==Kexi::DataViewMode); + +//moved setViewWidget(m_scrollView); +// m_scrollView->show(); + + m_dbform = new KexiDBForm(m_scrollView->viewport(), m_scrollView, name/*, conn*/); +// m_dbform->resize( m_scrollView->viewport()->size() - QSize(20, 20) ); +// m_dbform->resize(QSize(400, 300)); + m_scrollView->setWidget(m_dbform); + m_scrollView->setResizingEnabled(viewMode()!=Kexi::DataViewMode); + +// initForm(); + + if (viewMode()==Kexi::DataViewMode) { + m_scrollView->recordNavigator()->setRecordHandler( m_scrollView ); + m_scrollView->viewport()->setPaletteBackgroundColor(m_dbform->palette().active().background()); +//moved to formmanager connect(formPart()->manager(), SIGNAL(noFormSelected()), SLOT(slotNoFormSelected())); + } + else + { + connect(KFormDesigner::FormManager::self(), SIGNAL(propertySetSwitched(KoProperty::Set*, bool, const QCString&)), + this, SLOT(slotPropertySetSwitched(KoProperty::Set*, bool, const QCString&))); + connect(KFormDesigner::FormManager::self(), SIGNAL(dirty(KFormDesigner::Form *, bool)), + this, SLOT(slotDirty(KFormDesigner::Form *, bool))); + + connect(m_dbform, SIGNAL(handleDragMoveEvent(QDragMoveEvent*)), + this, SLOT(slotHandleDragMoveEvent(QDragMoveEvent*))); + connect(m_dbform, SIGNAL(handleDropEvent(QDropEvent*)), + this, SLOT(slotHandleDropEvent(QDropEvent*))); + + // action stuff + plugSharedAction("formpart_taborder", KFormDesigner::FormManager::self(), SLOT(editTabOrder())); + plugSharedAction("formpart_adjust_size", KFormDesigner::FormManager::self(), SLOT(adjustWidgetSize())); +//TODO plugSharedAction("formpart_pixmap_collection", formPart()->manager(), SLOT(editFormPixmapCollection())); +//TODO plugSharedAction("formpart_connections", formPart()->manager(), SLOT(editConnections())); + + plugSharedAction("edit_copy", KFormDesigner::FormManager::self(), SLOT(copyWidget())); + plugSharedAction("edit_cut", KFormDesigner::FormManager::self(), SLOT(cutWidget())); + plugSharedAction("edit_paste", KFormDesigner::FormManager::self(), SLOT(pasteWidget())); + plugSharedAction("edit_delete", KFormDesigner::FormManager::self(), SLOT(deleteWidget())); + plugSharedAction("edit_select_all", KFormDesigner::FormManager::self(), SLOT(selectAll())); + plugSharedAction("formpart_clear_contents", KFormDesigner::FormManager::self(), SLOT(clearWidgetContent())); + plugSharedAction("edit_undo", KFormDesigner::FormManager::self(), SLOT(undo())); + plugSharedAction("edit_redo", KFormDesigner::FormManager::self(), SLOT(redo())); + + plugSharedAction("formpart_layout_menu", KFormDesigner::FormManager::self(), 0 ); + plugSharedAction("formpart_layout_hbox", KFormDesigner::FormManager::self(), SLOT(layoutHBox()) ); + plugSharedAction("formpart_layout_vbox", KFormDesigner::FormManager::self(), SLOT(layoutVBox()) ); + plugSharedAction("formpart_layout_grid", KFormDesigner::FormManager::self(), SLOT(layoutGrid()) ); +#ifdef KEXI_SHOW_SPLITTER_WIDGET + plugSharedAction("formpart_layout_hsplitter", KFormDesigner::FormManager::self(), SLOT(layoutHSplitter()) ); + plugSharedAction("formpart_layout_vsplitter", KFormDesigner::FormManager::self(), SLOT(layoutVSplitter()) ); +#endif + plugSharedAction("formpart_break_layout", KFormDesigner::FormManager::self(), SLOT(breakLayout()) ); + + plugSharedAction("formpart_format_raise", KFormDesigner::FormManager::self(), SLOT(bringWidgetToFront()) ); + plugSharedAction("formpart_format_lower", KFormDesigner::FormManager::self(), SLOT(sendWidgetToBack()) ); + + plugSharedAction("other_widgets_menu", KFormDesigner::FormManager::self(), 0 ); + setAvailable("other_widgets_menu", true); + + plugSharedAction("formpart_align_menu", KFormDesigner::FormManager::self(), 0 ); + plugSharedAction("formpart_align_to_left", KFormDesigner::FormManager::self(),SLOT(alignWidgetsToLeft()) ); + plugSharedAction("formpart_align_to_right", KFormDesigner::FormManager::self(), SLOT(alignWidgetsToRight()) ); + plugSharedAction("formpart_align_to_top", KFormDesigner::FormManager::self(), SLOT(alignWidgetsToTop()) ); + plugSharedAction("formpart_align_to_bottom", KFormDesigner::FormManager::self(), SLOT(alignWidgetsToBottom()) ); + plugSharedAction("formpart_align_to_grid", KFormDesigner::FormManager::self(), SLOT(alignWidgetsToGrid()) ); + + plugSharedAction("formpart_adjust_size_menu", KFormDesigner::FormManager::self(), 0 ); + plugSharedAction("formpart_adjust_to_fit", KFormDesigner::FormManager::self(), SLOT(adjustWidgetSize()) ); + plugSharedAction("formpart_adjust_size_grid", KFormDesigner::FormManager::self(), SLOT(adjustSizeToGrid()) ); + plugSharedAction("formpart_adjust_height_small", KFormDesigner::FormManager::self(), SLOT(adjustHeightToSmall()) ); + plugSharedAction("formpart_adjust_height_big", KFormDesigner::FormManager::self(), SLOT(adjustHeightToBig()) ); + plugSharedAction("formpart_adjust_width_small", KFormDesigner::FormManager::self(), SLOT(adjustWidthToSmall()) ); + plugSharedAction("formpart_adjust_width_big", KFormDesigner::FormManager::self(), SLOT(adjustWidthToBig()) ); + + plugSharedAction("format_font", KFormDesigner::FormManager::self(), SLOT(changeFont()) ); + } + + initForm(); + + KexiDataAwareView::init( m_scrollView, m_scrollView, m_scrollView, + /* skip data-awarness if design mode */ viewMode()==Kexi::DesignViewMode ); + + connect(this, SIGNAL(focus(bool)), this, SLOT(slotFocus(bool))); + /// @todo skip this if ther're no borders +// m_dbform->resize( m_dbform->size()+QSize(m_scrollView->verticalScrollBar()->width(), m_scrollView->horizontalScrollBar()->height()) ); +} + +KexiFormView::~KexiFormView() +{ + if (m_cursor) { + KexiDB::Connection *conn = parentDialog()->mainWin()->project()->dbConnection(); + conn->deleteCursor(m_cursor); + m_cursor = 0; + } + deleteQuery(); + + // Important: form window is closed. + // Set property set to 0 because there is *only one* instance of a property set class + // in Kexi, so the main window wouldn't know the set in fact has been changed. + m_propertySet = 0; + propertySetSwitched(); +} + +void +KexiFormView::deleteQuery() +{ + if (m_cursor) { + KexiDB::Connection *conn = parentDialog()->mainWin()->project()->dbConnection(); + conn->deleteCursor(m_cursor); + m_cursor = 0; + } + + if (m_queryIsOwned) { + delete m_query; + } else { +//! @todo remove this shared query from listened queries list + } + m_query = 0; +} + +KFormDesigner::Form* +KexiFormView::form() const +{ + if(viewMode()==Kexi::DataViewMode) + return tempData()->previewForm; + else + return tempData()->form; +} + +void +KexiFormView::setForm(KFormDesigner::Form *f) +{ + if(viewMode()==Kexi::DataViewMode) + tempData()->previewForm = f; + else + tempData()->form = f; +} + +void +KexiFormView::initForm() +{ + setForm( new KFormDesigner::Form(KexiFormPart::library(), 0, viewMode()==Kexi::DesignViewMode) ); +// if (viewMode()==Kexi::DataViewMode) + //form()->setDesignMode(false); + form()->createToplevel(m_dbform, m_dbform); + + if (viewMode()==Kexi::DesignViewMode) { + //we want to be informed about executed commands + connect(form()->commandHistory(), SIGNAL(commandExecuted()), + KFormDesigner::FormManager::self(), SLOT(slotHistoryCommandExecuted())); + } + + const bool newForm = parentDialog()->id() < 0; + + KexiDB::FieldList *fields = 0; + if (newForm) { + // Show the form wizard if this is a new Form +#ifndef NO_DSWIZARD + KexiDataSourceWizard *w = new KexiDataSourceWizard(mainWin(), (QWidget*)mainWin(), "datasource_wizard"); + if(!w->exec()) + fields = 0; + else + fields = w->fields(); + delete w; +#endif + } + + if(fields) + { + QDomDocument dom; + formPart()->generateForm(fields, dom); + KFormDesigner::FormIO::loadFormFromDom(form(), m_dbform, dom); + //! @todo handle errors + } + else + loadForm(); + + if(form()->autoTabStops()) + form()->autoAssignTabStops(); + + //collect tab order information + m_dbform->updateTabStopsOrder(form()); + +// if (m_dbform->orderedFocusWidgets()->first()) + // m_scrollView->setFocusProxy( m_dbform->orderedFocusWidgets()->first() ); + + KFormDesigner::FormManager::self()->importForm(form(), viewMode()==Kexi::DataViewMode); + m_scrollView->setForm(form()); + +// m_dbform->updateTabStopsOrder(form()); +// QSize s = m_dbform->size(); +// QApplication::sendPostedEvents(); +// m_scrollView->resize( s ); +// m_dbform->resize(s); + m_scrollView->refreshContentsSize(); +// m_scrollView->refreshContentsSizeLater(true,true); + + if (newForm && !fields) { + /* Our form's area will be resized more than once. + Let's resize form widget itself later. */ + m_delayedFormContentsResizeOnShow = 3; + } + + updateDataSourcePage(); + + if (!newForm && viewMode()==Kexi::DesignViewMode) { + form()->clearCommandHistory(); + } +} + +void KexiFormView::updateAutoFieldsDataSource() +{ +//! @todo call this when form's data source is changed + //update autofields: + //-inherit captions + //-inherit data types + //(this data has not been stored in the form) + QString dataSourceString( m_dbform->dataSource() ); + QCString dataSourceMimeTypeString( m_dbform->dataSourceMimeType() ); + KexiDB::Connection *conn = parentDialog()->mainWin()->project()->dbConnection(); + KexiDB::TableOrQuerySchema tableOrQuery( + conn, dataSourceString.latin1(), dataSourceMimeTypeString=="kexi/table"); + if (!tableOrQuery.table() && !tableOrQuery.query()) + return; + for (KFormDesigner::ObjectTreeDictIterator it(*form()->objectTree()->dict()); + it.current(); ++it) + { + KexiDBAutoField *afWidget = dynamic_cast<KexiDBAutoField*>( it.current()->widget() ); + if (afWidget) { + KexiDB::QueryColumnInfo *colInfo = tableOrQuery.columnInfo( afWidget->dataSource() ); + if (colInfo) { + afWidget->setColumnInfo(colInfo); + //setFieldTypeInternal((int)colInfo->field->type()); + //afWidget->setFieldCaptionInternal(colInfo->captionOrAliasOrName()); + } + } + } +} + +void KexiFormView::updateValuesForSubproperties() +{ +//! @todo call this when form's data source is changed + //update autofields: + //-inherit captions + //-inherit data types + //(this data has not been stored in the form) + QString dataSourceString( m_dbform->dataSource() ); + QCString dataSourceMimeTypeString( m_dbform->dataSourceMimeType() ); + KexiDB::Connection *conn = parentDialog()->mainWin()->project()->dbConnection(); + KexiDB::TableOrQuerySchema tableOrQuery( + conn, dataSourceString.latin1(), dataSourceMimeTypeString=="kexi/table"); + if (!tableOrQuery.table() && !tableOrQuery.query()) + return; + + for (KFormDesigner::ObjectTreeDictIterator it(*form()->objectTree()->dict()); + it.current(); ++it) + { + // (delayed) set values for subproperties +//! @todo this could be at the KFD level, but KFD is going to be merged anyway with kexiforms, right? + KFormDesigner::WidgetWithSubpropertiesInterface* subpropIface + = dynamic_cast<KFormDesigner::WidgetWithSubpropertiesInterface*>( it.current()->widget() ); + if (subpropIface && subpropIface->subwidget() && it.current()->subproperties() ) { + QWidget *subwidget = subpropIface->subwidget(); + QMap<QString, QVariant>* subprops = it.current()->subproperties(); + for (QMapConstIterator<QString, QVariant> subpropIt = subprops->constBegin(); subpropIt!=subprops->constEnd(); ++subpropIt) { + kexipluginsdbg << "KexiFormView::loadForm(): delayed setting of the subproperty: widget=" + << it.current()->widget()->name() << " prop=" << subpropIt.key() << " val=" << subpropIt.data() << endl; + + const int count = subwidget->metaObject()->findProperty(subpropIt.key().latin1(), true); + const QMetaProperty *meta = count!=-1 ? subwidget->metaObject()->property(count, true) : 0; + if (meta) { + // Special case: the property value of type enum (set) but is saved as a string list, + // not as int, so we need to translate it to int. It's been created as such + // by FormIO::readPropertyValue(). Example: "alignment" property. + if (meta->isSetType() && subpropIt.data().type()==QVariant::StringList) { + QStrList keys; + const QStringList list( subpropIt.data().toStringList() ); + for (QStringList::ConstIterator it = list.constBegin(); it != list.constEnd(); ++it) + keys.append((*it).latin1()); + subwidget->setProperty( subpropIt.key().latin1(), meta->keysToValue(keys) ); + } + else { + subwidget->setProperty( subpropIt.key().latin1(), subpropIt.data() ); + } + } + }//for + } + } +} + +//! Used in KexiFormView::loadForm() +static void setUnsavedBLOBIdsForDataViewMode( + QWidget* widget, const QMap<QCString, KexiBLOBBuffer::Id_t>& unsavedLocalBLOBsByName) +{ + if (-1 != widget->metaObject()->findProperty("pixmapId")) { + const KexiBLOBBuffer::Id_t blobID = unsavedLocalBLOBsByName[ widget->name() ]; + if (blobID > 0) + widget->setProperty("pixmapId", (uint /* KexiBLOBBuffer::Id_t is unsafe and unsupported by QVariant - will be fixed in Qt4*/)blobID); + } + const QObjectList *list = widget->children(); + if (!list) + return; + for (QObjectListIterator it(*list); it.current(); ++it) { + if (dynamic_cast<QWidget*>(it.current())) + setUnsavedBLOBIdsForDataViewMode(dynamic_cast<QWidget*>(it.current()), unsavedLocalBLOBsByName); + } +} + +void +KexiFormView::loadForm() +{ +//@todo also load m_resizeMode ! + + kexipluginsdbg << "KexiFormView::loadForm() Loading the form with id : " << parentDialog()->id() << endl; + // If we are previewing the Form, use the tempData instead of the form stored in the db + if(viewMode()==Kexi::DataViewMode && !tempData()->tempForm.isNull() ) + { + KFormDesigner::FormIO::loadFormFromString(form(), m_dbform, tempData()->tempForm); + setUnsavedBLOBIdsForDataViewMode( m_dbform, tempData()->unsavedLocalBLOBsByName ); + updateAutoFieldsDataSource(); + updateValuesForSubproperties(); + return; + } + + // normal load + QString data; + loadDataBlock(data); + KFormDesigner::FormIO::loadFormFromString(form(), m_dbform, data); + + //"autoTabStops" property is loaded -set it within the form tree as well + form()->setAutoTabStops( m_dbform->autoTabStops() ); + + updateAutoFieldsDataSource(); + updateValuesForSubproperties(); +} + +void +KexiFormView::slotPropertySetSwitched(KoProperty::Set *set, bool forceReload, const QCString& propertyToSelect) +{ +// if (set && parentDialog()!=parentDialog()->mainWin()->currentDialog()) + if (form() != KFormDesigner::FormManager::self()->activeForm()) + return; //this is not the current form view + m_propertySet = set; + if (forceReload) + propertySetReloaded(true/*preservePrevSelection*/, propertyToSelect); + else + propertySetSwitched(); + + formPart()->dataSourcePage()->assignPropertySet(m_propertySet); +} + +tristate +KexiFormView::beforeSwitchTo(int mode, bool &dontStore) +{ + if (mode!=viewMode()) { + if (viewMode()==Kexi::DataViewMode) { + if (!m_scrollView->acceptRowEdit()) + return cancelled; + + m_scrollView->beforeSwitchView(); + } + else { + //remember our pos + tempData()->scrollViewContentsPos + = QPoint(m_scrollView->contentsX(), m_scrollView->contentsY()); + } + } + + // we don't store on db, but in our TempData + dontStore = true; + if(dirty() && (mode == Kexi::DataViewMode) && form()->objectTree()) { + KexiFormPart::TempData* temp = tempData(); + if (!KFormDesigner::FormIO::saveFormToString(form(), temp->tempForm)) + return false; + + //collect blobs from design mode by name for use in data view mode + temp->unsavedLocalBLOBsByName.clear(); + for (QMapConstIterator<QWidget*, KexiBLOBBuffer::Id_t> it = temp->unsavedLocalBLOBs.constBegin(); + it!=temp->unsavedLocalBLOBs.constEnd(); ++it) + { + if (!it.key()) + continue; + temp->unsavedLocalBLOBsByName.insert( it.key()->name(), it.data() ); + } + } + + return true; +} + +tristate +KexiFormView::afterSwitchFrom(int mode) +{ + if (mode == 0 || mode == Kexi::DesignViewMode) { + if (parentDialog()->neverSaved()) { + m_dbform->resize(QSize(400, 300)); + m_scrollView->refreshContentsSizeLater(true,true); + //m_delayedFormContentsResizeOnShow = false; + } + } + + if (mode != 0 && mode != Kexi::DesignViewMode) { + //preserve contents pos after switching to other view + m_scrollView->setContentsPos(tempData()->scrollViewContentsPos.x(), + tempData()->scrollViewContentsPos.y()); + } +// if (mode == Kexi::DesignViewMode) { + //m_scrollView->move(0,0); + //m_scrollView->setContentsPos(0,0); + //m_scrollView->moveChild(m_dbform, 0, 0); +// } + + if((mode == Kexi::DesignViewMode) && viewMode()==Kexi::DataViewMode) { + // The form may have been modified, so we must recreate the preview + delete m_dbform; // also deletes form() + m_dbform = new KexiDBForm(m_scrollView->viewport(), m_scrollView, "KexiDBForm"); + m_scrollView->setWidget(m_dbform); + + initForm(); +//moved to formmanager slotNoFormSelected(); + + //reset position + m_scrollView->setContentsPos(0,0); + m_dbform->move(0,0); + + } + + //update tab stops if needed + if (viewMode()==Kexi::DataViewMode) { +// //propagate current "autoTabStops" property value to the form tree +// form()->setAutoTabStops( m_dbform->autoTabStops() ); + +// if(form()->autoTabStops()) +// form()->autoAssignTabStops(); + } + else { + //set "autoTabStops" property + m_dbform->setAutoTabStops( form()->autoTabStops() ); + } + + if (viewMode() == Kexi::DataViewMode) { +//TMP!! + initDataSource(); + + //handle events for this form + m_scrollView->setMainWidgetForEventHandling(parentDialog()->mainWin(), m_dbform); + + //set focus on 1st focusable widget which has valid dataSource property set + if (!m_dbform->orderedFocusWidgets()->isEmpty()) { +// QWidget *www = focusWidget(); + //if (Kexi::hasParent(this, qApp->focusWidget())) { + KexiUtils::unsetFocusWithReason(qApp->focusWidget(), QFocusEvent::Tab); + //} + + QPtrListIterator<QWidget> it(*m_dbform->orderedFocusWidgets()); + for (;it.current(); ++it) { + KexiFormDataItemInterface *iface = dynamic_cast<KexiFormDataItemInterface*>(it.current()); + if (iface) + kexipluginsdbg << iface->dataSource() << endl; + if (iface && iface->columnInfo() && !iface->isReadOnly() +/*! @todo add option for skipping autoincremented fields */ + /* also skip autoincremented fields:*/ + && !iface->columnInfo()->field->isAutoIncrement()) //!iface->dataSource().isEmpty() + break; + } + if (!it.current()) //eventually, focus first available widget if nothing other is available + it.toFirst(); + + it.current()->setFocus(); + KexiUtils::setFocusWithReason(it.current(), QFocusEvent::Tab); + m_setFocusInternalOnce = it.current(); + } + + if (m_query) + m_scrollView->selectFirstRow(); + } + + //dirty only if it's a new object + if (mode == 0) + setDirty( parentDialog()->partItem()->neverSaved() ); + + if (mode==Kexi::DataViewMode && viewMode()==Kexi::DesignViewMode) { +// slotPropertySetSwitched +// emit KFormDesigner::FormManager::self()->propertySetSwitched( KFormDesigner::FormManager::self()->propertySet()->set(), true ); + } + + return true; +} + +void KexiFormView::initDataSource() +{ + deleteQuery(); + QString dataSourceString( m_dbform->dataSource() ); + QCString dataSourceMimeTypeString( m_dbform->dataSourceMimeType() ); +//! @todo also handle anonymous (not stored) queries provided as statements here + bool ok = !dataSourceString.isEmpty(); + +/* if (m_previousDataSourceString.lower()==dataSourceString.lower() && !m_cursor) { + //data source changed: delete previous cursor + m_conn->deleteCursor(m_cursor); + m_cursor = 0; + }*/ + + KexiDB::TableSchema *tableSchema = 0; + KexiDB::Connection *conn = 0; + QStringList sources; + bool forceReadOnlyDataSource = false; + + if (ok) { +// m_previousDataSourceString = dataSourceString; + + //collect all data-aware widgets and create query schema + m_scrollView->setMainDataSourceWidget(m_dbform); + sources = m_scrollView->usedDataSources(); + conn = parentDialog()->mainWin()->project()->dbConnection(); + if (dataSourceMimeTypeString.isEmpty() /*table type is the default*/ + || dataSourceMimeTypeString=="kexi/table") + { + tableSchema = conn->tableSchema( dataSourceString ); + if (tableSchema) { + /* We will build a _minimum_ query schema from selected table fields. */ + m_query = new KexiDB::QuerySchema(); + m_queryIsOwned = true; + + if (dataSourceMimeTypeString.isEmpty()) + m_dbform->setDataSourceMimeType("kexi/table"); //update for compatibility + } + } + + if (!tableSchema) { + if (dataSourceMimeTypeString.isEmpty() /*also try to find a query (for compatibility with Kexi<=0.9)*/ + || dataSourceMimeTypeString=="kexi/query") + { + //try to find predefined query schema. + //Note: In general, we could not skip unused fields within this query because + // it can have GROUP BY clause. + //! @todo check if the query could have skipped unused fields (no GROUP BY, no joins, etc.) + m_query = conn->querySchema( dataSourceString ); + m_queryIsOwned = false; + ok = m_query != 0; + if (ok && dataSourceMimeTypeString.isEmpty()) + m_dbform->setDataSourceMimeType("kexi/query"); //update for compatibility + // query results are read-only +//! @todo There can be read-write queries, e.g. simple "SELECT * FROM...". Add a checking function to KexiDB. + forceReadOnlyDataSource = true; + } + else //no other mime types supported + ok = false; + } + } + + QDict<char> invalidSources(997); + if (ok) { + KexiDB::IndexSchema *pkey = tableSchema ? tableSchema->primaryKey() : 0; + if (pkey) { + //always add all fields from table's primary key + // (don't worry about duplicates, unique list will be computed later) + sources += pkey->names(); + kexipluginsdbg << "KexiFormView::initDataSource(): pkey added to data sources: " << pkey->names() << endl; + } + kexipluginsdbg << "KexiFormView::initDataSource(): sources=" << sources << endl; + + uint index = 0; + for (QStringList::ConstIterator it = sources.constBegin(); + it!=sources.constEnd(); ++it, index++) { +/*! @todo add expression support */ + QString fieldName( (*it).lower() ); + //remove "tablename." if it was prepended + if (tableSchema && fieldName.startsWith( tableSchema->name().lower()+"." )) + fieldName = fieldName.mid(tableSchema->name().length()+1); + //remove "queryname." if it was prepended + if (!tableSchema && fieldName.startsWith( m_query->name().lower()+"." )) + fieldName = fieldName.mid(m_query->name().length()+1); + KexiDB::Field *f = tableSchema ? tableSchema->field(fieldName) : m_query->field(fieldName); + if (!f) { +/*! @todo show error */ + //remove this widget from the set of data widgets in the provider +/*! @todo fieldName is ok, but what about expressions? */ + invalidSources.insert( fieldName, (const char*)1 ); // += index; + kexipluginsdbg << "KexiFormView::initDataSource(): invalidSources+=" << index << " (" + << (*it) << ")" << endl; + continue; + } + if (tableSchema) { + if (!m_query->hasField( f )) { + //we're building a new query: add this field + m_query->addField( f ); + } + } + } + if (invalidSources.count()==sources.count()) { + //all data sources are invalid! don't execute the query + deleteQuery(); + } + else { + KexiDB::debug( m_query->parameters() ); + // like in KexiQueryView::executeQuery() + QValueList<QVariant> params; + { + KexiUtils::WaitCursorRemover remover; + params = KexiQueryParameters::getParameters(this, *conn->driver(), *m_query, ok); + } + if (ok) //input cancelled + m_cursor = conn->executeQuery( *m_query, params ); + } + m_scrollView->invalidateDataSources( invalidSources, m_query ); + ok = m_cursor!=0; + } + + if (!invalidSources.isEmpty()) + m_dbform->updateTabStopsOrder(); + + if (ok) { +//! @todo PRIMITIVE!! data setting: +//! @todo KexiTableViewData is not great name for data class here... rename/move? + KexiTableViewData* data = new KexiTableViewData(m_cursor); + if (forceReadOnlyDataSource) + data->setReadOnly(true); + data->preloadAllRows(); + +///*! @todo few backends return result count for free! - no need to reopen() */ +// int resultCount = -1; +// if (ok) { +// resultCount = m_conn->resultCount(m_conn->selectStatement(*m_query)); +// ok = m_cursor->reopen(); +// } +// if (ok) +// ok = ! (!m_cursor->moveFirst() && m_cursor->error()); + + m_scrollView->setData( data, true /*owner*/ ); + } + else + m_scrollView->setData( 0, false ); +} + +void +KexiFormView::slotDirty(KFormDesigner::Form *dirtyForm, bool isDirty) +{ + if(dirtyForm == form()) + KexiViewBase::setDirty(isDirty); +} + +KexiDB::SchemaData* +KexiFormView::storeNewData(const KexiDB::SchemaData& sdata, bool &cancel) +{ + KexiDB::SchemaData *s = KexiViewBase::storeNewData(sdata, cancel); + kexipluginsdbg << "KexiDBForm::storeNewData(): new id:" << s->id() << endl; + + if (!s || cancel) { + delete s; + return 0; + } + if (!storeData()) { + //failure: remove object's schema data to avoid garbage + KexiDB::Connection *conn = parentDialog()->mainWin()->project()->dbConnection(); + conn->removeObject( s->id() ); + delete s; + return 0; + } + return s; +} + +tristate +KexiFormView::storeData(bool dontAsk) +{ + Q_UNUSED(dontAsk); + kexipluginsdbg << "KexiDBForm::storeData(): " << parentDialog()->partItem()->name() + << " [" << parentDialog()->id() << "]" << endl; + + //-- first, store local BLOBs, so identifiers can be updated +//! @todo remove unused data stored previously + KexiDB::Connection *conn = parentDialog()->mainWin()->project()->dbConnection(); + KexiDB::TableSchema *blobsTable = conn->tableSchema("kexi__blobs"); + if (!blobsTable) { //compatibility check for older Kexi project versions +//! @todo show message about missing kexi__blobs? + return false; + } + // Not all engines accept passing NULL to PKEY o_id, so we're omitting it. + QStringList blobsFieldNamesWithoutID(blobsTable->names()); + blobsFieldNamesWithoutID.pop_front(); + KexiDB::FieldList *blobsFieldsWithoutID = blobsTable->subList(blobsFieldNamesWithoutID); + + KexiDB::PreparedStatement::Ptr st = conn->prepareStatement( + KexiDB::PreparedStatement::InsertStatement, *blobsFieldsWithoutID); + if (!st) { + delete blobsFieldsWithoutID; + //! @todo show message + return false; + } + KexiBLOBBuffer *blobBuf = KexiBLOBBuffer::self(); + KexiFormView *designFormView + = dynamic_cast<KexiFormView*>( parentDialog()->viewForMode(Kexi::DesignViewMode) ); + if (designFormView) { + for (QMapConstIterator<QWidget*, KexiBLOBBuffer::Id_t> it = tempData()->unsavedLocalBLOBs.constBegin(); + it!=tempData()->unsavedLocalBLOBs.constEnd(); ++it) + { + if (!it.key()) { + kexipluginswarn << "KexiFormView::storeData(): it.key()==0 !" << endl; + continue; + } + kexipluginsdbg << "name=" << it.key()->name() << " dataID=" << it.data() << endl; + KexiBLOBBuffer::Handle h( blobBuf->objectForId(it.data(), /*!stored*/false) ); + if (!h) + continue; //no BLOB assigned + + QString originalFileName(h.originalFileName()); + QFileInfo fi(originalFileName); + QString caption(fi.baseName().replace('_', " ").simplifyWhiteSpace()); + + if (st) { + *st /* << NO, (pgsql doesn't support this):QVariant()*/ /*id*/ + << h.data() << originalFileName << caption + << h.mimeType() << (uint)/*! @todo unsafe */h.folderId(); + if (!st->execute()) { + delete blobsFieldsWithoutID; + kexipluginsdbg << " execute error" << endl; + return false; + } + } + delete blobsFieldsWithoutID; + blobsFieldsWithoutID=0; + const Q_ULLONG storedBLOBID = conn->lastInsertedAutoIncValue("o_id", "kexi__blobs"); + if ((Q_ULLONG)-1 == storedBLOBID) { + //! @todo show message? + return false; + } + kexipluginsdbg << " storedDataID=" << storedBLOBID << endl; + h.setStoredWidthID((KexiBLOBBuffer::Id_t /*unsafe - will be fixed in Qt4*/)storedBLOBID); + //set widget's internal property so it can be saved... + const QVariant oldStoredPixmapId( it.key()->property("storedPixmapId") ); + it.key()->setProperty("storedPixmapId", + QVariant((uint /* KexiBLOBBuffer::Id_t is unsafe and unsupported by QVariant - will be fixed in Qt4*/)storedBLOBID)); + KFormDesigner::ObjectTreeItem *widgetItem = designFormView->form()->objectTree()->lookup(it.key()->name()); //form()->objectTree()->lookup(it.key()->name()); + if (widgetItem) + widgetItem->addModifiedProperty( "storedPixmapId", oldStoredPixmapId ); + else + kexipluginswarn << "KexiFormView::storeData(): no '" << widgetItem->name() << "' widget found within a form" << endl; + } + } + + //-- now, save form's XML + QString data; + if (!KFormDesigner::FormIO::saveFormToString(tempData()->form, data)) + return false; + if (!storeDataBlock(data)) + return false; + + //all blobs are now saved + tempData()->unsavedLocalBLOBs.clear(); + + tempData()->tempForm = QString::null; + return true; +} + +#if 0 +/// Action stuff ///////////////// +void +KexiFormView::slotWidgetSelected(KFormDesigner::Form *f, bool multiple) +{ + if(f != form()) + return; + + enableFormActions(); + // Enable edit actions + setAvailable("edit_copy", true); + setAvailable("edit_cut", true); + setAvailable("edit_clear", true); + + // 'Align Widgets' menu + setAvailable("formpart_align_menu", multiple); + setAvailable("formpart_align_to_left", multiple); + setAvailable("formpart_align_to_right", multiple); + setAvailable("formpart_align_to_top", multiple); + setAvailable("formpart_align_to_bottom", multiple); + + setAvailable("formpart_adjust_size_menu", true); + setAvailable("formpart_adjust_width_small", multiple); + setAvailable("formpart_adjust_width_big", multiple); + setAvailable("formpart_adjust_height_small", multiple); + setAvailable("formpart_adjust_height_big", multiple); + + setAvailable("formpart_format_raise", true); + setAvailable("formpart_format_lower", true); + + // If the widgets selected is a container, we enable layout actions + if(!multiple) + { + KFormDesigner::ObjectTreeItem *item = f->objectTree()->lookup( f->selectedWidgets()->first()->name() ); + if(item && item->container()) + multiple = true; + } + // Layout actions + setAvailable("formpart_layout_hbox", multiple); + setAvailable("formpart_layout_vbox", multiple); + setAvailable("formpart_layout_grid", multiple); + + KFormDesigner::Container *container = f->activeContainer(); + setAvailable("formpart_break_layout", container ? + (container->layoutType() != KFormDesigner::Container::NoLayout) : false ); +} + +void +KexiFormView::slotFormWidgetSelected(KFormDesigner::Form *f) +{ + if(f != form()) + return; + + disableWidgetActions(); + enableFormActions(); + + // Layout actions + setAvailable("formpart_layout_hbox", true); + setAvailable("formpart_layout_vbox", true); + setAvailable("formpart_layout_grid", true); + setAvailable("formpart_break_layout", (f->toplevelContainer()->layoutType() != KFormDesigner::Container::NoLayout)); +} + +void +KexiFormView::slotNoFormSelected() // == form in preview mode +{ + disableWidgetActions(); + + // Disable paste action + setAvailable("edit_paste", false); + setAvailable("edit_undo", false); + setAvailable("edit_redo", false); + + // Disable 'Tools' actions + setAvailable("formpart_pixmap_collection", false); + setAvailable("formpart_connections", false); + setAvailable("formpart_taborder", false); + setAvailable("formpart_change_style", false); +} + +void +KexiFormView::enableFormActions() +{ + // Enable 'Tools' actions + setAvailable("formpart_pixmap_collection", true); + setAvailable("formpart_connections", true); + setAvailable("formpart_taborder", true); + + setAvailable("edit_paste", KFormDesigner::FormManager::self()->isPasteEnabled()); +} + +void +KexiFormView::disableWidgetActions() +{ + // Disable edit actions + setAvailable("edit_copy", false); + setAvailable("edit_cut", false); + setAvailable("edit_clear", false); + + // Disable format functions + setAvailable("formpart_align_menu", false); + setAvailable("formpart_align_to_left", false); + setAvailable("formpart_align_to_right", false); + setAvailable("formpart_align_to_top", false); + setAvailable("formpart_align_to_bottom", false); + + setAvailable("formpart_adjust_size_menu", false); + setAvailable("formpart_adjust_width_small", false); + setAvailable("formpart_adjust_width_big", false); + setAvailable("formpart_adjust_height_small", false); + setAvailable("formpart_adjust_height_big", false); + + setAvailable("formpart_format_raise", false); + setAvailable("formpart_format_lower", false); + + setAvailable("formpart_layout_hbox", false); + setAvailable("formpart_layout_vbox", false); + setAvailable("formpart_layout_grid", false); + setAvailable("formpart_break_layout", false); +} + +void +KexiFormView::setUndoEnabled(bool enabled) +{ + setAvailable("edit_undo", enabled); +} + +void +KexiFormView::setRedoEnabled(bool enabled) +{ + setAvailable("edit_redo", enabled); +} +#endif //0 + +QSize +KexiFormView::preferredSizeHint(const QSize& otherSize) +{ + if (parentDialog()->neverSaved()) { + //ignore otherSize if possible +// return KexiViewBase::preferredSizeHint( (parentDialog() && parentDialog()->mdiParent()) ? QSize(10000,10000) : otherSize); + } + + return (m_dbform->size() + +QSize(m_scrollView->verticalScrollBar()->isVisible() ? m_scrollView->verticalScrollBar()->width()*3/2 : 10, + m_scrollView->horizontalScrollBar()->isVisible() ? m_scrollView->horizontalScrollBar()->height()*3/2 : 10)) + .expandedTo( KexiViewBase::preferredSizeHint(otherSize) ); +} + +void +KexiFormView::resizeEvent( QResizeEvent *e ) +{ + if (viewMode()==Kexi::DataViewMode) { + m_scrollView->refreshContentsSizeLater( + e->size().width()!=e->oldSize().width(), + e->size().height()!=e->oldSize().height() + ); + } + KexiViewBase::resizeEvent(e); + m_scrollView->updateNavPanelGeometry(); + if (m_delayedFormContentsResizeOnShow>0) { // && isVisible()) { + m_delayedFormContentsResizeOnShow--; + m_dbform->resize( e->size() - QSize(30, 30) ); + } +} + +void +KexiFormView::setFocusInternal() +{ + if (viewMode() == Kexi::DataViewMode) { + if (m_dbform->focusWidget()) { + //better-looking focus + if (m_setFocusInternalOnce) { + KexiUtils::setFocusWithReason(m_setFocusInternalOnce, QFocusEvent::Other);//Tab); + m_setFocusInternalOnce = 0; + } + else { + //ok? SET_FOCUS_USING_REASON(m_dbform->focusWidget(), QFocusEvent::Other);//Tab); + } + return; + } + } + QWidget::setFocus(); +} + +void +KexiFormView::show() +{ + KexiDataAwareView::show(); + +//moved from KexiFormScrollView::show(): + + //now get resize mode settings for entire form + // if (resizeMode() == KexiFormView::ResizeAuto) + if (viewMode()==Kexi::DataViewMode) { + if (resizeMode() == KexiFormView::ResizeAuto) + m_scrollView->setResizePolicy(QScrollView::AutoOneFit); + } +} + +void +KexiFormView::slotFocus(bool in) +{ + if(in && form() && KFormDesigner::FormManager::self() && KFormDesigner::FormManager::self()->activeForm() != form()) { + KFormDesigner::FormManager::self()->windowChanged(m_dbform); + updateDataSourcePage(); + } +} + +void +KexiFormView::updateDataSourcePage() +{ + if (viewMode()==Kexi::DesignViewMode) { + QCString dataSourceMimeType, dataSource; + KFormDesigner::WidgetPropertySet *set = KFormDesigner::FormManager::self()->propertySet(); + if (set->contains("dataSourceMimeType")) + dataSourceMimeType = (*set)["dataSourceMimeType"].value().toCString(); + if (set->contains("dataSource")) + dataSource = (*set)["dataSource"].value().toCString(); + + formPart()->dataSourcePage()->setDataSource(dataSourceMimeType, dataSource); + } +} + +void +KexiFormView::slotHandleDragMoveEvent(QDragMoveEvent* e) +{ + if (KexiFieldDrag::canDecodeMultiple( e )) { + e->accept(true); + //dirty: drawRect(QRect( e->pos(), QSize(50, 20)), 2); + } +} + +void +KexiFormView::slotHandleDropEvent(QDropEvent* e) +{ + const QWidget *targetContainerWidget = dynamic_cast<const QWidget*>(sender()); + KFormDesigner::ObjectTreeItem *targetContainerWidgetItem = targetContainerWidget + ? form()->objectTree()->lookup( targetContainerWidget->name() ) : 0; + if (targetContainerWidgetItem && targetContainerWidgetItem->container() + && KexiFieldDrag::canDecodeMultiple( e )) + { + QString sourceMimeType, sourceName; + QStringList fields; + if (!KexiFieldDrag::decodeMultiple( e, sourceMimeType, sourceName, fields )) + return; + insertAutoFields(sourceMimeType, sourceName, fields, + targetContainerWidgetItem->container(), e->pos()); + } +} + +void +KexiFormView::insertAutoFields(const QString& sourceMimeType, const QString& sourceName, + const QStringList& fields, KFormDesigner::Container* targetContainer, const QPoint& _pos) +{ + if (fields.isEmpty()) + return; + + KexiDB::Connection *conn = parentDialog()->mainWin()->project()->dbConnection(); + KexiDB::TableOrQuerySchema tableOrQuery(conn, sourceName.latin1(), sourceMimeType=="kexi/table"); + if (!tableOrQuery.table() && !tableOrQuery.query()) { + kexipluginswarn << "KexiFormView::insertAutoFields(): no such table/query \"" + << sourceName << "\"" << endl; + return; + } + + QPoint pos(_pos); + //if pos is not specified, compute a new position: + if (pos==QPoint(-1,-1)) { + if (m_widgetGeometryForRecentInsertAutoFields.isValid()) { + pos = m_widgetGeometryForRecentInsertAutoFields.bottomLeft() + + QPoint(0,form()->gridSize()); + } + else { + pos = QPoint(40, 40); //start here + } + } + + // there will be many actions performed, do not update property pane until all that's finished + KFormDesigner::FormManager::self()->blockPropertyEditorUpdating(this); + +//! todo unnamed query colums are not supported + +// KFormDesigner::WidgetList* prevSelection = form()->selectedWidgets(); + KFormDesigner::WidgetList widgetsToSelect; + KFormDesigner::CommandGroup *group = new KFormDesigner::CommandGroup( + fields.count()==1 ? i18n("Insert AutoField widget") : i18n("Insert %1 AutoField widgets").arg(fields.count()), + KFormDesigner::FormManager::self()->propertySet() + ); + + foreach( QStringList::ConstIterator, it, fields ) { + KexiDB::QueryColumnInfo* column = tableOrQuery.columnInfo(*it); + if (!column) { + kexipluginswarn << "KexiFormView::insertAutoFields(): no such field \"" + << *it << "\" in table/query \"" << sourceName << "\"" << endl; + continue; + } +//! todo add autolabel using field's caption or name + //KFormDesigner::Container *targetContainer; +/* QWidget* targetContainerWidget = QApplication::widgetAt(pos, true); + while (targetContainerWidget + && !dynamic_cast<KFormDesigner::Container*>(targetContainerWidget)) + { + targetContainerWidget = targetContainerWidget->parentWidget(); + } + if (dynamic_cast<KFormDesigner::Container*>(targetContainerWidget)) + targetContainer = dynamic_cast<KFormDesigner::Container*>(targetContainerWidget); + else + targetContainer = form()->toplevelContainer();*/ + KFormDesigner::InsertWidgetCommand *insertCmd + = new KFormDesigner::InsertWidgetCommand(targetContainer, + //! todo this is hardcoded! + "KexiDBAutoField", + //! todo this name can be invalid for expressions: if so, fall back to a default class' prefix! + pos, column->aliasOrName() + ); + insertCmd->execute(); + group->addCommand(insertCmd, false/*don't exec twice*/); + + KFormDesigner::ObjectTreeItem *newWidgetItem + = form()->objectTree()->dict()->find(insertCmd->widgetName()); + KexiDBAutoField* newWidget + = newWidgetItem ? dynamic_cast<KexiDBAutoField*>(newWidgetItem->widget()) : 0; + widgetsToSelect.append(newWidget); +//#if 0 + KFormDesigner::CommandGroup *subGroup + = new KFormDesigner::CommandGroup("", KFormDesigner::FormManager::self()->propertySet()); + QMap<QCString, QVariant> propValues; + propValues.insert("dataSource", column->aliasOrName()); + propValues.insert("fieldTypeInternal", (int)column->field->type()); + propValues.insert("fieldCaptionInternal", column->captionOrAliasOrName()); + KFormDesigner::FormManager::self()->propertySet()->createPropertyCommandsInDesignMode( + newWidget, propValues, subGroup, false/*!addToActiveForm*/, + true /*!execFlagForSubCommands*/); + subGroup->execute(); + group->addCommand( subGroup, false/*will not be executed on CommandGroup::execute()*/ ); + +//#endif + //set data source and caption + //-we don't need to use PropertyCommand here beacause we don't need UNDO + // for these single commands +// newWidget->setDataSource(column->aliasOrName()); +// newWidget->setFieldTypeInternal((int)column->field->type()); +// newWidget->setFieldCaptionInternal(column->captionOrAliasOrName()); + //resize again because autofield's type changed what can lead to changed sizeHint() +// newWidget->resize(newWidget->sizeHint()); + KFormDesigner::WidgetList list; + list.append(newWidget); + KFormDesigner::AdjustSizeCommand *adjustCommand + = new KFormDesigner::AdjustSizeCommand(KFormDesigner::AdjustSizeCommand::SizeToFit, + list, form()); + adjustCommand->execute(); + group->addCommand( adjustCommand, + false/*will not be executed on CommandGroup::execute()*/ + ); + + if (newWidget) {//move position down for next widget + pos.setY( pos.y() + newWidget->height() + form()->gridSize()); + } + } + if (widgetsToSelect.last()) { + //resize form if needed + QRect oldFormRect( m_dbform->geometry() ); + QRect newFormRect( oldFormRect ); + newFormRect.setWidth(QMAX(m_dbform->width(), widgetsToSelect.last()->geometry().right()+1)); + newFormRect.setHeight(QMAX(m_dbform->height(), widgetsToSelect.last()->geometry().bottom()+1)); + if (newFormRect != oldFormRect) { + //1. resize by hand + m_dbform->setGeometry( newFormRect ); + //2. store information about resize + KFormDesigner::PropertyCommand *resizeFormCommand = new KFormDesigner::PropertyCommand( + KFormDesigner::FormManager::self()->propertySet(), m_dbform->name(), + oldFormRect, newFormRect, "geometry"); + group->addCommand(resizeFormCommand, true/*will be executed on CommandGroup::execute()*/); + } + + //remember geometry of the last inserted widget + m_widgetGeometryForRecentInsertAutoFields = widgetsToSelect.last()->geometry(); + } + + //eventually, add entire command group to active form + form()->addCommand( group, true/*exec*/ ); + +// group->debug(); + + //enable proper REDO usage + group->resetAllowExecuteFlags(); + + m_scrollView->repaint(); + m_scrollView->viewport()->repaint(); + m_scrollView->repaintContents(); + m_scrollView->updateContents(); + m_scrollView->clipper()->repaint(); + m_scrollView->refreshContentsSize(); + + //select all inserted widgets, if multiple + if (widgetsToSelect.count()>1) { + form()->setSelectedWidget(0); + foreach_list (KFormDesigner::WidgetListIterator, it, widgetsToSelect) + form()->setSelectedWidget(it.current(), true/*add*/, true/*dontRaise*/); + } + + // eventually, update property pane + KFormDesigner::FormManager::self()->unblockPropertyEditorUpdating(this, KFormDesigner::FormManager::self()->propertySet()); +} + +void +KexiFormView::setUnsavedLocalBLOB(QWidget *widget, KexiBLOBBuffer::Id_t id) +{ +//! @todo if there already was data assigned, remember it should be dereferenced + if (id==0) + tempData()->unsavedLocalBLOBs.remove(widget); + else + tempData()->unsavedLocalBLOBs.insert(widget, id); +} + +/* +todo +void KexiFormView::updateActions(bool activated) +{ + if (viewMode()==Kexi::DesignViewMode) { + if (form()->selectedWidget()) { + if (form()->widget() == form()->selectedWidget()) + KFormDesigner::FormManager::self()->emitFormWidgetSelected( form() ); + else + KFormDesigner::FormManager::self()->emitWidgetSelected( form(), false ); + } + else if (form()->selectedWidgets()) { + KFormDesigner::FormManager::self()->emitWidgetSelected( form(), true ); + } + } + KexiDataAwareView::updateActions(activated); +}*/ + +/* +void KexiFormView::parentDialogDetached() +{ + m_dbform->updateTabStopsOrder(form()); +} + +void KexiFormView::parentDialogAttached(KMdiChildFrm *) +{ + m_dbform->updateTabStopsOrder(form()); +}*/ + +#include "kexiformview.moc" + diff --git a/kexi/plugins/forms/kexiformview.h b/kexi/plugins/forms/kexiformview.h new file mode 100644 index 00000000..0a774556 --- /dev/null +++ b/kexi/plugins/forms/kexiformview.h @@ -0,0 +1,231 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr> + Copyright (C) 2004-2007 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 KEXIFORMVIEW_H +#define KEXIFORMVIEW_H + +#include <qtimer.h> + +#include <kexiviewbase.h> +#include <widget/kexidataawareview.h> + +#include "kexiformpart.h" +#include <core/kexiblobbuffer.h> + +class KexiFormPart; +class KexiMainWindow; +class KexiDBForm; +class KexiTableItem; +class KexiTableViewData; +class KexiFormScrollView; +namespace KexiDB { class Cursor; } +namespace KFormDesigner +{ + class Container; +} + +//! The KexiFormView lass provides a data-driven (record-based) form view . +/*! The KexiFormView can display data provided "by hand" + or from KexiDB-compatible database source. + + This class provides a single view used inside KexiDialogBase. + It takes care of saving/loading form, of enabling actions when needed. + One KexiFormView object is instantiated for data view mode + and a second KexiFormView object is instantiated for design view mode. + + @see KexiDataTable +*/ +class KEXIFORMUTILS_EXPORT KexiFormView : public KexiDataAwareView +{ + Q_OBJECT + + public: + enum ResizeMode { + ResizeAuto = 0, + ResizeDefault = ResizeAuto, + ResizeFixed = 1, + NoResize = 2 /*! @todo */ + }; + +// KexiFormView(KexiMainWindow *win, QWidget *parent, const char *name, KexiDB::Connection *conn); + KexiFormView(KexiMainWindow *mainWin, QWidget *parent, const char *name = 0, + bool dbAware = true); + virtual ~KexiFormView(); + +// KexiDB::Connection* connection() { return m_conn; } + + virtual QSize preferredSizeHint(const QSize& otherSize); + + int resizeMode() const { return m_resizeMode; } + + KFormDesigner::Form* form() const; + + /*! Assigns \a id local (static) BLOB's identifier for \a widget widget. + Previously assigned BLOB will be usassigned. + If \a id is 0, BLOB is unassigned and no new is assigned. + + This method is called when a widget supporting BLOB data + (currently, images from KexiDBImageBox, within KexiDBFactory) has BLOB assigned by identifier \a id. + BLOB identifiers are defined by KexiBLOBBuffer (KexiBLOBBuffer::self() instance). + + The data collected by this method is used on form's design saving (in design mode). + Local BLOBs are retrieved KexiBLOBBuffer::self() and stored in "kexi__blobs" 'system' table. + Note that db-aware BLOBs (non local) are not handled this way. + */ + void setUnsavedLocalBLOB(QWidget *widget, KexiBLOBBuffer::Id_t id); + + public slots: + /*! Reimplemented to update resize policy. */ + virtual void show(); + + /*! Inserts autofields onto the form at \a pos position. + \a sourceMimeType can be "kexi/table" or "kexi/query", + \a sourceName is a name of a table or query, \a fields is a list of fields to insert (one or more) + Fields are inserted using standard KFormDesigner::InsertWidgetCommand framework, + so undo/redo is available for this operation. + + If multiple fields are provided, they will be aligned vertically. + If \a pos is QPoint(-1,-1) (the default), position is computed automatically + based on a position last inserted field using this method. + If this method has not been called yet, position of QPoint(40, 40) will be set. + + Called by: + - slotHandleDropEvent() when field(s) are dropped from the data source pane onto the form + - KexiFormManager is a used clicked "Insert fields" button on the data source pane. */ + void insertAutoFields(const QString& sourceMimeType, const QString& sourceName, + const QStringList& fields, KFormDesigner::Container* targetContainerWidget, + const QPoint& pos = QPoint(-1,-1)); + + protected slots: + void slotPropertySetSwitched(KoProperty::Set *b, bool forceReload = false, + const QCString& propertyToSelect = QCString()); + void slotDirty(KFormDesigner::Form *f, bool isDirty); + void slotFocus(bool in); + void slotHandleDragMoveEvent(QDragMoveEvent* e); + + //! Handles field(s) dropping from the data source pane onto the form + //! @see insertAutoFields() + void slotHandleDropEvent(QDropEvent* e); + +//moved to formmanager void slotWidgetSelected(KFormDesigner::Form *form, bool multiple); +//moved to formmanager void slotFormWidgetSelected(KFormDesigner::Form *form); +//moved to formmanager void slotNoFormSelected(); + +//moved to formmanager void setUndoEnabled(bool enabled); +//moved to formmanager void setRedoEnabled(bool enabled); + + protected: + virtual tristate beforeSwitchTo(int mode, bool &dontStore); + virtual tristate afterSwitchFrom(int mode); + virtual KoProperty::Set* propertySet() { return m_propertySet; } + + virtual KexiDB::SchemaData* storeNewData(const KexiDB::SchemaData& sdata, bool &cancel); + virtual tristate storeData(bool dontAsk = false); + + KexiFormPart::TempData* tempData() const { + return dynamic_cast<KexiFormPart::TempData*>(parentDialog()->tempData()); } + KexiFormPart* formPart() const { return dynamic_cast<KexiFormPart*>(part()); } + +//moved to formmanager void disableWidgetActions(); +//moved to formmanager void enableFormActions(); + + void setForm(KFormDesigner::Form *f); + + void initForm(); + + void loadForm(); + + //! Used in loadForm() + void updateAutoFieldsDataSource(); + + //! Used in loadForm() + void updateValuesForSubproperties(); + + virtual void resizeEvent ( QResizeEvent * ); + + void initDataSource(); + + virtual void setFocusInternal(); + +/* // for navigator + virtual void moveToRecordRequested(uint r); + virtual void moveToLastRecordRequested(); + virtual void moveToPreviousRecordRequested(); + virtual void moveToNextRecordRequested(); + virtual void moveToFirstRecordRequested(); + virtual void addNewRecordRequested();*/ + + /*! Called after loading the form contents (before showing it). + Also called when the form window (KexiDialogBase) is detached + (in KMDI's Child Frame mode), because otherwise tabstop ordering can get broken. */ + void updateTabStopsOrder(); + + /*! @internal */ + void deleteQuery(); + + /*! @internal */ + void updateDataSourcePage(); + + /*! Reimplemented after KexiViewBase. + Updates actions (e.g. availability). */ +// todo virtual void updateActions(bool activated); + + KexiDBForm *m_dbform; + KexiFormScrollView *m_scrollView; + KoProperty::Set *m_propertySet; + + /*! Database cursor used for data retrieving. + It is shared between subsequent Data view sessions (just reopened on switch), + but deleted and recreated from scratch when form's "dataSource" property changed + since last form viewing (m_previousDataSourceString is used for that). */ + QString m_previousDataSourceString; + + int m_resizeMode; + + KexiDB::QuerySchema* m_query; + + /*! True, if m_query is created as temporary object within this form. + If user selected an existing, predefined (stored) query, m_queryIsOwned will be false, + so the query object will not be destroyed. */ + bool m_queryIsOwned; + + KexiDB::Cursor *m_cursor; + + /*! For new (empty) forms only: + Our form's area will be resized more than once. + We will resize form widget itself later (in resizeEvent()). */ + int m_delayedFormContentsResizeOnShow; + + //! Used in setFocusInternal() + QGuardedPtr<QWidget> m_setFocusInternalOnce; + + + /*! Stores geometry of widget recently inserted using insertAutoFields() method. + having this information, we'r eable to compute position for a newly + inserted widget in insertAutoFields() is such position has not been specified. + (the position is specified when a widget is inserted with mouse drag & dropping + but not with clicking of 'Insert fields' button from Data Source pane) */ + QRect m_widgetGeometryForRecentInsertAutoFields; + + //! Used in setUnsavedLocalBLOBs() +// QMap<QWidget*, KexiBLOBBuffer::Id_t> m_unsavedLocalBLOBs; +}; + +#endif diff --git a/kexi/plugins/forms/kformdesigner_kexidbfactory.desktop b/kexi/plugins/forms/kformdesigner_kexidbfactory.desktop new file mode 100644 index 00000000..4e5bb719 --- /dev/null +++ b/kexi/plugins/forms/kformdesigner_kexidbfactory.desktop @@ -0,0 +1,55 @@ +[Desktop Entry] +Type=Service +ServiceTypes=KFormDesigner/WidgetFactory + +Name=Kexi DB Widgets +Name[bg]=Графични обекти на Kexi за бази данни +Name[ca]=Estris DB de Kexi +Name[cy]=Celfigion Cronfa Ddata Kexi +Name[da]=Kexi DB-kontroller +Name[de]=Kexi Datenbank-Elemente +Name[el]=Γραφικά συστατικά Kexi DB +Name[eo]=Kexi DB-fenestraĵo +Name[es]=Wigdet de BD de Kexi +Name[et]=Kexi andmebaasividinad +Name[eu]=Kexi-ren datu-baseko trepetak +Name[fa]=عناصر Kexi DB +Name[fi]=Kexi tietokantaelementit +Name[fr]=Éléments graphiques de base de données Kexi +Name[fy]=Kexi DB-widgets +Name[gl]=Elementos de Base de Datos Kexi +Name[he]=פריטי מסד נתונים של Kexi +Name[hr]=Kexi DB widgeti +Name[hu]=Kexi adatbázis-kezelési grafikus elemek +Name[is]=Kexi gagnagrunns hlutir +Name[it]=Oggetti per banche dati per Kexi +Name[ja]=Kexi DB ウィジェット +Name[km]=ធាតុក្រាហ្វិក DB សម្រាប់ Kexi +Name[lv]=Kexi DB logdaļas +Name[ms]=Widget DB Kexi +Name[nb]=DB-element for Kexi +Name[nds]=Datenbank-Stüerelementen för Kexi +Name[ne]=केक्सी DB विजेटहरू +Name[nl]=Kexi DB-widgets +Name[nn]=DB-element for Kexi +Name[pl]=Kontrolki baz danych dla Kexi +Name[pt]=Elementos de Base de Dados Kexi +Name[pt_BR]=Widgets de BD do Kexi +Name[ru]=Элементы управления для работы с базами данных Kexi +Name[se]=Kexi-DV-áđat +Name[sk]=Komponenty Kexi DB +Name[sl]=Gradniki za zbirko podatkov za Kexi +Name[sr]=Kexi-јеве DB контроле +Name[sr@Latn]=Kexi-jeve DB kontrole +Name[sv]=Kexi-databaskomponenter +Name[ta]=கெக்சி டிபி சாளர உருக்கள் +Name[tr]=Kexi DB Parçacıkları +Name[uk]=Віджети Kexi DB +Name[uz]=Kexi maʼlumot baza vidjetlari +Name[uz@cyrillic]=Kexi маълумот база виджетлари +Name[zh_CN]=Kexi 数据库部件 +Name[zh_TW]=Kexi DB 視窗元件 + +X-KDE-Library=kformdesigner_kexidbwidgets +X-KFormDesigner-FactoryGroup=kexi +X-KFormDesigner-WidgetFactoryVersion=2 diff --git a/kexi/plugins/forms/widgets/Makefile.am b/kexi/plugins/forms/widgets/Makefile.am new file mode 100644 index 00000000..5ca5cbd8 --- /dev/null +++ b/kexi/plugins/forms/widgets/Makefile.am @@ -0,0 +1,28 @@ +include $(top_srcdir)/kexi/Makefile.global + +noinst_LTLIBRARIES = libkexiformutilswidgets.la + +libkexiformutilswidgets_la_SOURCES = \ + kexidbutils.cpp \ + kexidbautofield.cpp \ + kexidbform.cpp \ + kexidbsubform.cpp \ + kexidblabel.cpp \ + kexidbimagebox.cpp \ + kexipushbutton.cpp \ + kexiframe.cpp \ + kexidblineedit.cpp \ + kexidbcheckbox.cpp \ + kexidbtextedit.cpp \ + kexidbcombobox.cpp + +libkexiformutilswidgets_la_LDFLAGS = $(all_libraries) -Wnounresolved +libkexiformutilswidgets_la_LIBADD = + +libkexiformutilswidgets_la_METASOURCES = AUTO + +SUBDIRS = . + +# set the include path for X, qt and KDE +INCLUDES= -I$(top_srcdir)/kexi -I$(top_srcdir)/kexi/plugins/forms -I$(top_srcdir)/kexi/core $(all_includes) + diff --git a/kexi/plugins/forms/widgets/kexidbautofield.cpp b/kexi/plugins/forms/widgets/kexidbautofield.cpp new file mode 100644 index 00000000..36fbdb1a --- /dev/null +++ b/kexi/plugins/forms/widgets/kexidbautofield.cpp @@ -0,0 +1,846 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr> + Copyright (C) 2005 Christian Nitschkowski <segfault_ii@web.de> + Copyright (C) 2005-2007 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 "kexidbautofield.h" + +#include <qlabel.h> +#include <qlayout.h> +#include <qpainter.h> +#include <qmetaobject.h> +#include <qapplication.h> + +#include <kdebug.h> +#include <klocale.h> + +#include "kexidbcheckbox.h" +#include "kexidbimagebox.h" +#include "kexidblabel.h" +#include "kexidblineedit.h" +#include "kexidbtextedit.h" +#include "kexidbcombobox.h" +#include "kexipushbutton.h" +#include "kexidbform.h" + +#include <kexidb/queryschema.h> +#include <formeditor/utils.h> +#include <kexiutils/utils.h> + +#define KexiDBAutoField_SPACING 10 //10 pixel for spacing between a label and an editor widget + +//! @internal +class KexiDBAutoField::Private +{ + public: + Private() + { + } + + WidgetType widgetType; //!< internal: equal to m_widgetType_property or equal to result + //!< of widgetTypeForFieldType() if widgetTypeForFieldType is Auto + WidgetType widgetType_property; //!< provides widget type or Auto + LabelPosition lblPosition; + QBoxLayout *layout; + QLabel *label; + QString caption; + KexiDB::Field::Type fieldTypeInternal; + QString fieldCaptionInternal; + QColor baseColor; //!< needed because for unbound mode editor==0 + QColor textColor; //!< needed because for unbound mode editor==0 + bool autoCaption : 1; + bool focusPolicyChanged : 1; + bool designMode : 1; +}; + +//------------------------------------- + +KexiDBAutoField::KexiDBAutoField(const QString &text, WidgetType type, LabelPosition pos, + QWidget *parent, const char *name, bool designMode) + : QWidget(parent, name) + , KexiFormDataItemInterface() + , KFormDesigner::DesignTimeDynamicChildWidgetHandler() + , d( new Private() ) +{ + d->designMode = designMode; + init(text, type, pos); +} + +KexiDBAutoField::KexiDBAutoField(QWidget *parent, const char *name, bool designMode, LabelPosition pos) + : QWidget(parent, name) + , KexiFormDataItemInterface() + , KFormDesigner::DesignTimeDynamicChildWidgetHandler() + , d( new Private() ) +{ + d->designMode = designMode; + init(QString::null/*i18n("Auto Field")*/, Auto, pos); +} + +KexiDBAutoField::~KexiDBAutoField() +{ + setUpdatesEnabled(false); + if (m_subwidget) + m_subwidget->setUpdatesEnabled(false); + delete d; +} + +void +KexiDBAutoField::init(const QString &text, WidgetType type, LabelPosition pos) +{ + d->fieldTypeInternal = KexiDB::Field::InvalidType; + d->layout = 0; + m_subwidget = 0; + d->label = new QLabel(text, this); + d->label->installEventFilter( this ); + //QFontMetrics fm( font() ); + //d->label->setFixedWidth( fm.width("This is a test string length") ); + d->autoCaption = true; + d->focusPolicyChanged = false; + d->widgetType = Auto; + d->widgetType_property = (type==Auto ? Text : type); //to force "differ" to be true in setWidgetType() + setLabelPosition(pos); + setWidgetType(type); + d->baseColor = palette().active().base(); + d->textColor = palette().active().text(); +} + +void +KexiDBAutoField::setWidgetType(WidgetType type) +{ + const bool differ = (type != d->widgetType_property); + d->widgetType_property = type; + if(differ) { + if(type == Auto) {// try to guess type from data source type + if (visibleColumnInfo()) + d->widgetType = KexiDBAutoField::widgetTypeForFieldType(visibleColumnInfo()->field->type()); + else + d->widgetType = Auto; + } + else + d->widgetType = d->widgetType_property; + createEditor(); + } +} + +void +KexiDBAutoField::createEditor() +{ + if(m_subwidget) { + delete (QWidget *)m_subwidget; + } + + QWidget *newSubwidget; + switch( d->widgetType ) { + case Text: + case Double: //! @todo setup validator + case Integer: //! @todo setup validator + case Date: + case Time: + case DateTime: + newSubwidget = new KexiDBLineEdit( this, QCString("KexiDBAutoField_KexiDBLineEdit:")+name() ); + break; + case MultiLineText: + newSubwidget = new KexiDBTextEdit( this, QCString("KexiDBAutoField_KexiDBTextEdit:")+name() ); + break; + case Boolean: + newSubwidget = new KexiDBCheckBox(dataSource(), this, QCString("KexiDBAutoField_KexiDBCheckBox:")+name()); + break; + case Image: + newSubwidget = new KexiDBImageBox(d->designMode, this, QCString("KexiDBAutoField_KexiDBImageBox:")+name()); + break; + case ComboBox: + newSubwidget = new KexiDBComboBox(this, QCString("KexiDBAutoField_KexiDBComboBox:")+name(), d->designMode); + break; + default: + newSubwidget = 0; + changeText(d->caption); + //d->label->setText( d->dataSource.isEmpty() ? "<datasource>" : d->dataSource ); + break; + } + + setSubwidget( newSubwidget ); //this will also allow to declare subproperties, see KFormDesigner::WidgetWithSubpropertiesInterface + if(newSubwidget) { + newSubwidget->setName( QCString("KexiDBAutoField_") + newSubwidget->className() ); + dynamic_cast<KexiDataItemInterface*>(newSubwidget)->setParentDataItemInterface(this); + dynamic_cast<KexiFormDataItemInterface*>(newSubwidget) + ->setColumnInfo(columnInfo()); //needed at least by KexiDBImageBox + dynamic_cast<KexiFormDataItemInterface*>(newSubwidget) + ->setVisibleColumnInfo(visibleColumnInfo()); //needed at least by KexiDBComboBox + newSubwidget->setProperty("dataSource", dataSource()); //needed at least by KexiDBImageBox + KFormDesigner::DesignTimeDynamicChildWidgetHandler::childWidgetAdded(this); + newSubwidget->show(); + d->label->setBuddy(newSubwidget); + if (d->focusPolicyChanged) {//if focusPolicy is changed at top level, editor inherits it + newSubwidget->setFocusPolicy(focusPolicy()); + } + else {//if focusPolicy is not changed at top level, inherit it from editor + QWidget::setFocusPolicy(newSubwidget->focusPolicy()); + } + setFocusProxy(newSubwidget); //ok? + if (parentWidget()) + newSubwidget->setPalette( qApp->palette() ); + copyPropertiesToEditor(); +// KFormDesigner::installRecursiveEventFilter(newSubwidget, this); + } + + setLabelPosition(labelPosition()); +} + +void KexiDBAutoField::copyPropertiesToEditor() +{ + if (m_subwidget) { +// kdDebug() << "KexiDBAutoField::copyPropertiesToEditor(): base col: " << d->baseColor.name() << +// "; text col: " << d->textColor.name() << endl; + QPalette p( m_subwidget->palette() ); + p.setColor( QPalette::Active, QColorGroup::Base, d->baseColor ); + if(d->widgetType == Boolean) + p.setColor( QPalette::Active, QColorGroup::Foreground, d->textColor ); + else + p.setColor( QPalette::Active, QColorGroup::Text, d->textColor ); + m_subwidget->setPalette(p); + //m_subwidget->setPaletteBackgroundColor( d->baseColor ); + } +} + +void +KexiDBAutoField::setLabelPosition(LabelPosition position) +{ + d->lblPosition = position; + if(d->layout) { + QBoxLayout *lyr = d->layout; + d->layout = 0; + delete lyr; + } + + if(m_subwidget) + m_subwidget->show(); + //! \todo support right-to-left layout where positions are inverted + if (position==Top || position==Left) { + int align = d->label->alignment(); + if(position == Top) { + d->layout = (QBoxLayout*) new QVBoxLayout(this); + align |= AlignVertical_Mask; + align ^= AlignVertical_Mask; + align |= AlignTop; + } + else { + d->layout = (QBoxLayout*) new QHBoxLayout(this); + align |= AlignVertical_Mask; + align ^= AlignVertical_Mask; + align |= AlignVCenter; + } + d->label->setAlignment(align); + if(d->widgetType == Boolean + || (d->widgetType == Auto && fieldTypeInternal() == KexiDB::Field::InvalidType && !d->designMode)) + { + d->label->hide(); + } + else { + d->label->show(); + } + d->layout->addWidget(d->label, 0, position == Top ? int(Qt::AlignLeft) : 0); + if(position == Left && d->widgetType != Boolean) + d->layout->addSpacing(KexiDBAutoField_SPACING); + d->layout->addWidget(m_subwidget, 1); + KexiSubwidgetInterface *subwidgetInterface = dynamic_cast<KexiSubwidgetInterface*>((QWidget*)m_subwidget); + if (subwidgetInterface) { + if (subwidgetInterface->appendStretchRequired(this)) + d->layout->addStretch(0); + if (subwidgetInterface->subwidgetStretchRequired(this)) { + QSizePolicy sizePolicy( m_subwidget->sizePolicy() ); + if(position == Left) { + sizePolicy.setHorData( QSizePolicy::Minimum ); + d->label->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); + } + else { + sizePolicy.setVerData( QSizePolicy::Minimum ); + d->label->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); + } + m_subwidget->setSizePolicy(sizePolicy); + } + } +// if(m_subwidget) + // m_subwidget->setSizePolicy(...); + } + else { + d->layout = (QBoxLayout*) new QHBoxLayout(this); + d->label->hide(); + d->layout->addWidget(m_subwidget); + } + //a hack to force layout to be refreshed (any better idea for this?) + resize(size()+QSize(1,0)); + resize(size()-QSize(1,0)); + if (dynamic_cast<KexiDBAutoField*>((QWidget*)m_subwidget)) { + //needed for KexiDBComboBox + dynamic_cast<KexiDBAutoField*>((QWidget*)m_subwidget)->setLabelPosition(position); + } +} + +void +KexiDBAutoField::setInvalidState( const QString &text ) +{ + // Widget with an invalid dataSource is just a QLabel + if (d->designMode) + return; + d->widgetType = Auto; + createEditor(); + setFocusPolicy(QWidget::NoFocus); + if (m_subwidget) + m_subwidget->setFocusPolicy(QWidget::NoFocus); +//! @todo or set this to editor's text? + d->label->setText( text ); +} + +bool +KexiDBAutoField::isReadOnly() const +{ + KexiFormDataItemInterface *iface = dynamic_cast<KexiFormDataItemInterface*>((QWidget*)m_subwidget); + if(iface) + return iface->isReadOnly(); + else + return false; +} + +void +KexiDBAutoField::setReadOnly( bool readOnly ) +{ + KexiFormDataItemInterface *iface = dynamic_cast<KexiFormDataItemInterface*>((QWidget*)m_subwidget); + if(iface) + iface->setReadOnly(readOnly); +} + +void +KexiDBAutoField::setValueInternal(const QVariant& add, bool removeOld) +{ + KexiFormDataItemInterface *iface = dynamic_cast<KexiFormDataItemInterface*>((QWidget*)m_subwidget); + if(iface) + iface->setValue(m_origValue, add, removeOld); +// iface->setValueInternal(add, removeOld); +} + +QVariant +KexiDBAutoField::value() +{ + KexiFormDataItemInterface *iface = dynamic_cast<KexiFormDataItemInterface*>((QWidget*)m_subwidget); + if(iface) + return iface->value(); + return QVariant(); +} + +bool +KexiDBAutoField::valueIsNull() +{ + KexiFormDataItemInterface *iface = dynamic_cast<KexiFormDataItemInterface*>((QWidget*)m_subwidget); + if(iface) + return iface->valueIsNull(); + return true; +} + +bool +KexiDBAutoField::valueIsEmpty() +{ + KexiFormDataItemInterface *iface = dynamic_cast<KexiFormDataItemInterface*>((QWidget*)m_subwidget); + if(iface) + return iface->valueIsEmpty(); + return true; +} + +bool +KexiDBAutoField::valueIsValid() +{ + KexiFormDataItemInterface *iface = dynamic_cast<KexiFormDataItemInterface*>((QWidget*)m_subwidget); + if(iface) + return iface->valueIsValid(); + return true; +} + +bool +KexiDBAutoField::valueChanged() +{ + KexiFormDataItemInterface *iface = dynamic_cast<KexiFormDataItemInterface*>((QWidget*)m_subwidget); + kexipluginsdbg << m_origValue << endl; + if(iface) + return iface->valueChanged(); + return false; +} + +void +KexiDBAutoField::installListener(KexiDataItemChangesListener* listener) +{ + KexiFormDataItemInterface::installListener(listener); + KexiFormDataItemInterface *iface = dynamic_cast<KexiFormDataItemInterface*>((QWidget*)m_subwidget); + if(iface) + iface->installListener(listener); +} + +KexiDBAutoField::WidgetType KexiDBAutoField::widgetType() const +{ + return d->widgetType_property; +} + +KexiDBAutoField::LabelPosition KexiDBAutoField::labelPosition() const +{ + return d->lblPosition; +} + +QString KexiDBAutoField::caption() const +{ + return d->caption; +} + +bool KexiDBAutoField::hasAutoCaption() const +{ + return d->autoCaption; +} + +QWidget* KexiDBAutoField::editor() const +{ + return m_subwidget; +} + +QLabel* KexiDBAutoField::label() const +{ + return d->label; +} + +int KexiDBAutoField::fieldTypeInternal() const +{ + return d->fieldTypeInternal; +} + +QString KexiDBAutoField::fieldCaptionInternal() const +{ + return d->fieldCaptionInternal; +} + +bool +KexiDBAutoField::cursorAtStart() +{ + KexiFormDataItemInterface *iface = dynamic_cast<KexiFormDataItemInterface*>((QWidget*)m_subwidget); + if(iface) + return iface->cursorAtStart(); + return false; +} + +bool +KexiDBAutoField::cursorAtEnd() +{ + KexiFormDataItemInterface *iface = dynamic_cast<KexiFormDataItemInterface*>((QWidget*)m_subwidget); + if(iface) + return iface->cursorAtEnd(); + return false; +} + +void +KexiDBAutoField::clear() +{ + KexiFormDataItemInterface *iface = dynamic_cast<KexiFormDataItemInterface*>((QWidget*)m_subwidget); + if(iface) + iface->clear(); +} + +void +KexiDBAutoField::setFieldTypeInternal(int kexiDBFieldType) +{ + d->fieldTypeInternal = (KexiDB::Field::Type)kexiDBFieldType; + KexiDB::Field::Type fieldType; + //find real fied type to use + if (d->fieldTypeInternal==KexiDB::Field::InvalidType) { + if (visibleColumnInfo()) + fieldType = KexiDB::Field::Text; + else + fieldType = KexiDB::Field::InvalidType; + } + else + fieldType = d->fieldTypeInternal; + + const WidgetType newWidgetType = KexiDBAutoField::widgetTypeForFieldType( fieldType ); + + if(d->widgetType != newWidgetType) { + d->widgetType = newWidgetType; + createEditor(); + } + setFieldCaptionInternal(d->fieldCaptionInternal); +} + +void +KexiDBAutoField::setFieldCaptionInternal(const QString& text) +{ + d->fieldCaptionInternal = text; + //change text only if autocaption is set and no columnInfo is available + KexiFormDataItemInterface *iface = dynamic_cast<KexiFormDataItemInterface*>((QWidget*)m_subwidget); + if((!iface || !iface->columnInfo()) && d->autoCaption) { + changeText(d->fieldCaptionInternal); + } +} + +void +KexiDBAutoField::setColumnInfo(KexiDB::QueryColumnInfo* cinfo) +{ + KexiFormDataItemInterface::setColumnInfo(cinfo); + setColumnInfoInternal(cinfo, cinfo); +} + +void +KexiDBAutoField::setColumnInfoInternal(KexiDB::QueryColumnInfo* cinfo, KexiDB::QueryColumnInfo* visibleColumnInfo) +{ + // change widget type depending on field type + if(d->widgetType_property == Auto) { + WidgetType newWidgetType = Auto; + KexiDB::Field::Type fieldType; + if (cinfo) + fieldType = visibleColumnInfo->field->type(); + else if (dataSource().isEmpty()) + fieldType = KexiDB::Field::InvalidType; + else + fieldType = KexiDB::Field::Text; + + if (fieldType != KexiDB::Field::InvalidType) { + newWidgetType = KexiDBAutoField::widgetTypeForFieldType( fieldType ); + } + if(d->widgetType != newWidgetType || newWidgetType==Auto) { + d->widgetType = newWidgetType; + createEditor(); + } + } + // update label's text + changeText((cinfo && d->autoCaption) ? cinfo->captionOrAliasOrName() : d->caption); + + KexiFormDataItemInterface *iface = dynamic_cast<KexiFormDataItemInterface*>((QWidget*)m_subwidget); + if(iface) + iface->setColumnInfo(visibleColumnInfo); +} + +//static +KexiDBAutoField::WidgetType +KexiDBAutoField::widgetTypeForFieldType(KexiDB::Field::Type type) +{ + switch(type) { + case KexiDB::Field::Integer: + case KexiDB::Field::ShortInteger: + case KexiDB::Field::BigInteger: + return Integer; + case KexiDB::Field::Boolean: + return Boolean; + case KexiDB::Field::Float: + case KexiDB::Field::Double: + return Double; + case KexiDB::Field::Date: + return Date; + case KexiDB::Field::DateTime: + return DateTime; + case KexiDB::Field::Time: + return Time; + case KexiDB::Field::Text: + return Text; + case KexiDB::Field::LongText: + return MultiLineText; + case KexiDB::Field::Enum: + return ComboBox; + case KexiDB::Field::InvalidType: + return Auto; + case KexiDB::Field::BLOB: + return Image; + default: + break; + } + return Text; +} + +void +KexiDBAutoField::changeText(const QString &text, bool beautify) +{ + QString realText; + bool unbound = false; + if (d->autoCaption && (d->widgetType==Auto || dataSource().isEmpty())) { + if (d->designMode) + realText = QString::fromLatin1(name())+" "+i18n("Unbound Auto Field", "(unbound)"); + else + realText = QString::null; + unbound = true; + } + else { + if (beautify) { + /*! @todo look at appendColonToAutoLabels setting [bool] + @todo look at makeFirstCharacterUpperCaseInCaptions setting [bool] + (see doc/dev/settings.txt) */ + if (!text.isEmpty()) { + realText = text[0].upper() + text.mid(1); + if (d->widgetType!=Boolean) { +//! @todo ":" suffix looks weird for checkbox; remove this condition when [x] is displayed _after_ label +//! @todo support right-to-left layout where position of ":" is inverted + realText += ": "; + } + } + } + else + realText = text; + } + + if (unbound) + d->label->setAlignment( Qt::AlignCenter | Qt::WordBreak ); + else + d->label->setAlignment( Qt::AlignCenter ); +// QWidget* widgetToAlterForegroundColor; + if(d->widgetType == Boolean) { + static_cast<QCheckBox*>((QWidget*)m_subwidget)->setText(realText); +// widgetToAlterForegroundColor = m_subwidget; + } + else { + d->label->setText(realText); +// widgetToAlterForegroundColor = d->label; + } +/* + if (unbound) + widgetToAlterForegroundColor->setPaletteForegroundColor( + KexiUtils::blendedColors( + widgetToAlterForegroundColor->paletteForegroundColor(), + widgetToAlterForegroundColor->paletteBackgroundColor(), 2, 1)); + else + widgetToAlterForegroundColor->setPaletteForegroundColor( paletteForegroundColor() );*/ +} + +void +KexiDBAutoField::setCaption(const QString &caption) +{ + d->caption = caption; + if(!d->autoCaption && !caption.isEmpty()) + changeText(d->caption); +} + +void +KexiDBAutoField::setAutoCaption(bool autoCaption) +{ + d->autoCaption = autoCaption; + if(d->autoCaption) { + //d->caption = QString::null; + if(columnInfo()) { + changeText(columnInfo()->captionOrAliasOrName()); + } + else { + changeText(d->fieldCaptionInternal); + } + } + else + changeText(d->caption); +} + +void +KexiDBAutoField::setDataSource( const QString &ds ) { + KexiFormDataItemInterface::setDataSource(ds); + if (ds.isEmpty()) { + setColumnInfo(0); + } +} + +QSize +KexiDBAutoField::sizeHint() const +{ + if (d->lblPosition == NoLabel) + return m_subwidget ? m_subwidget->sizeHint() : QWidget::sizeHint(); + + QSize s1(0,0); + if (m_subwidget) + s1 = m_subwidget->sizeHint(); + QSize s2(d->label->sizeHint()); + if (d->lblPosition == Top) + return QSize(QMAX(s1.width(), s2.width()), s1.height()+KexiDBAutoField_SPACING+s2.height()); + + //left + return QSize(s1.width()+KexiDBAutoField_SPACING+s2.width(), QMAX(s1.height(), s2.height())); +} + +void +KexiDBAutoField::setFocusPolicy( FocusPolicy policy ) +{ + d->focusPolicyChanged = true; + QWidget::setFocusPolicy(policy); + d->label->setFocusPolicy(policy); + if (m_subwidget) + m_subwidget->setFocusPolicy(policy); +} + +void +KexiDBAutoField::updateInformationAboutUnboundField() +{ + if ( (d->autoCaption && (dataSource().isEmpty() || dataSourceMimeType().isEmpty())) + || (!d->autoCaption && d->caption.isEmpty()) ) + { + d->label->setText( QString::fromLatin1(name())+" "+i18n("Unbound Auto Field", " (unbound)") ); + } +// else +// d->label->setText( QString::fromLatin1(name())+" "+i18n(" (unbound)") ); +} + +/*void +KexiDBAutoField::paintEvent( QPaintEvent* pe ) +{ + QWidget::paintEvent( pe ); + + if ( (d->autoCaption && (dataSource().isEmpty() || dataSourceMimeType().isEmpty())) + || (!d->autoCaption && d->caption.isEmpty()) ) + { + QPainter p(this); + p.setPen( d->label->paletteForegroundColor() ); + p.setClipRect(pe->rect()); + p.setFont(d->label->font()); + p.drawText(rect(), Qt::AlignLeft | Qt::WordBreak, + QString::fromLatin1(name())+" "+i18n(" (unbound)")); + } +}*/ + +void +KexiDBAutoField::paletteChange( const QPalette& oldPal ) +{ + Q_UNUSED(oldPal); + d->label->setPalette( palette() ); +} + +void KexiDBAutoField::unsetPalette() +{ + QWidget::unsetPalette(); + +} + +// ===== methods below are just proxies for the internal editor or label ===== + +const QColor & KexiDBAutoField::paletteForegroundColor() const +{ + return d->textColor; +} + +void KexiDBAutoField::setPaletteForegroundColor( const QColor & color ) +{ + d->textColor = color; + copyPropertiesToEditor(); +} + +const QColor & KexiDBAutoField::paletteBackgroundColor() const +{ + return d->baseColor; +} + +void KexiDBAutoField::setPaletteBackgroundColor( const QColor & color ) +{ + d->baseColor = color; + copyPropertiesToEditor(); +} + +const QColor & KexiDBAutoField::foregroundLabelColor() const +{ + if(d->widgetType == Boolean) + return paletteForegroundColor(); + + return d->label->paletteForegroundColor(); +} + +void KexiDBAutoField::setForegroundLabelColor( const QColor & color ) +{ + if(d->widgetType == Boolean) + setPaletteForegroundColor(color); + else { + d->label->setPaletteForegroundColor(color); + QWidget::setPaletteForegroundColor(color); + } +} + +const QColor & KexiDBAutoField::backgroundLabelColor() const +{ + if(d->widgetType == Boolean) + return paletteBackgroundColor(); + + return d->label->paletteBackgroundColor(); +} + +void KexiDBAutoField::setBackgroundLabelColor( const QColor & color ) +{ + if(d->widgetType == Boolean) + setPaletteBackgroundColor(color); + else { + d->label->setPaletteBackgroundColor(color); + QWidget::setPaletteBackgroundColor(color); + } + +// if (m_subwidget) +// m_subwidget->setPalette( qApp->palette() ); +} + +QVariant KexiDBAutoField::property( const char * name ) const +{ + bool ok; + QVariant val = KFormDesigner::WidgetWithSubpropertiesInterface::subproperty(name, ok); + if (ok) + return val; + return QWidget::property(name); +} + +bool KexiDBAutoField::setProperty( const char * name, const QVariant & value ) +{ + bool ok = KFormDesigner::WidgetWithSubpropertiesInterface::setSubproperty(name, value); + if (ok) + return true; + return QWidget::setProperty(name, value); +} + +bool KexiDBAutoField::eventFilter( QObject *o, QEvent *e ) +{ + if (o==d->label && d->label->buddy() && e->type()==QEvent::MouseButtonRelease) { + //focus label's buddy when user clicked the label + d->label->buddy()->setFocus(); + } + return QWidget::eventFilter(o, e); +} + +void KexiDBAutoField::setDisplayDefaultValue(QWidget* widget, bool displayDefaultValue) +{ + KexiFormDataItemInterface::setDisplayDefaultValue(widget, displayDefaultValue); + if (dynamic_cast<KexiFormDataItemInterface*>((QWidget*)m_subwidget)) + dynamic_cast<KexiFormDataItemInterface*>((QWidget*)m_subwidget)->setDisplayDefaultValue(m_subwidget, displayDefaultValue); +} + +void KexiDBAutoField::moveCursorToEnd() +{ + KexiDataItemInterface *iface = dynamic_cast<KexiDataItemInterface*>((QWidget*)m_subwidget); + if (iface) + iface->moveCursorToEnd(); +} + +void KexiDBAutoField::moveCursorToStart() +{ + KexiDataItemInterface *iface = dynamic_cast<KexiDataItemInterface*>((QWidget*)m_subwidget); + if (iface) + iface->moveCursorToStart(); +} + +void KexiDBAutoField::selectAll() +{ + KexiDataItemInterface *iface = dynamic_cast<KexiDataItemInterface*>((QWidget*)m_subwidget); + if (iface) + iface->selectAll(); +} + +bool KexiDBAutoField::keyPressed(QKeyEvent *ke) +{ + KexiFormDataItemInterface *iface = dynamic_cast<KexiFormDataItemInterface*>((QWidget*)m_subwidget); + if (iface && iface->keyPressed(ke)) + return true; + return false; +} + +#include "kexidbautofield.moc" diff --git a/kexi/plugins/forms/widgets/kexidbautofield.h b/kexi/plugins/forms/widgets/kexidbautofield.h new file mode 100644 index 00000000..981a0519 --- /dev/null +++ b/kexi/plugins/forms/widgets/kexidbautofield.h @@ -0,0 +1,210 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr> + Copyright (C) 2005 Christian Nitschkowski <segfault_ii@web.de> + Copyright (C) 2005-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 KEXIDBAUTOFIELD_H +#define KEXIDBAUTOFIELD_H + +#include <qwidget.h> +#include <kexidb/field.h> +#include <formeditor/container.h> +#include <formeditor/widgetwithsubpropertiesinterface.h> +#include "kexiformdataiteminterface.h" + +class QBoxLayout; +class QLabel; + +//! Universal "Auto Field" widget for Kexi forms +/*! It acts as a container for most data-aware widgets. */ +class KEXIFORMUTILS_EXPORT KexiDBAutoField : + public QWidget, + public KexiFormDataItemInterface, + public KFormDesigner::DesignTimeDynamicChildWidgetHandler, + public KFormDesigner::WidgetWithSubpropertiesInterface +{ + Q_OBJECT +//'caption' is uncovered now Q_PROPERTY(QString labelCaption READ caption WRITE setCaption DESIGNABLE true) + Q_OVERRIDE(QString caption READ caption WRITE setCaption DESIGNABLE true) + Q_OVERRIDE(QColor paletteForegroundColor READ paletteForegroundColor WRITE setPaletteForegroundColor DESIGNABLE true RESET unsetPalette) + Q_OVERRIDE(QColor paletteBackgroundColor READ paletteBackgroundColor WRITE setPaletteBackgroundColor DESIGNABLE true RESET unsetPalette) + Q_PROPERTY(QColor foregroundLabelColor READ foregroundLabelColor WRITE setForegroundLabelColor DESIGNABLE true RESET unsetPalette) + Q_PROPERTY(QColor backgroundLabelColor READ backgroundLabelColor WRITE setBackgroundLabelColor DESIGNABLE true RESET unsetPalette) + Q_PROPERTY(bool autoCaption READ hasAutoCaption WRITE setAutoCaption DESIGNABLE true) + Q_PROPERTY(QString dataSource READ dataSource WRITE setDataSource DESIGNABLE true) + Q_PROPERTY(QCString dataSourceMimeType READ dataSourceMimeType WRITE setDataSourceMimeType DESIGNABLE true) + Q_PROPERTY( bool readOnly READ isReadOnly WRITE setReadOnly ) + Q_PROPERTY(LabelPosition labelPosition READ labelPosition WRITE setLabelPosition DESIGNABLE true) + Q_PROPERTY(WidgetType widgetType READ widgetType WRITE setWidgetType DESIGNABLE true) + /*internal, for design time only*/ + Q_PROPERTY(int fieldTypeInternal READ fieldTypeInternal WRITE setFieldTypeInternal DESIGNABLE true STORED false) + Q_PROPERTY(QString fieldCaptionInternal READ fieldCaptionInternal WRITE setFieldCaptionInternal DESIGNABLE true STORED false) + Q_ENUMS( WidgetType LabelPosition ) + + public: + enum WidgetType { Auto = 100, Text, Integer, Double, Boolean, Date, Time, DateTime, + MultiLineText, ComboBox, Image }; + enum LabelPosition { Left = 300, Top, NoLabel }; + + KexiDBAutoField(const QString &text, WidgetType type, LabelPosition pos, + QWidget *parent = 0, const char *name = 0, bool designMode = true); + KexiDBAutoField(QWidget *parent = 0, const char *name = 0, bool designMode = true, + LabelPosition pos = Left); + + virtual ~KexiDBAutoField(); + + inline QString dataSource() const { return KexiFormDataItemInterface::dataSource(); } + inline QCString dataSourceMimeType() const { return KexiFormDataItemInterface::dataSourceMimeType(); } + virtual void setDataSource( const QString &ds ); + virtual void setDataSourceMimeType(const QCString &ds) { KexiFormDataItemInterface::setDataSourceMimeType(ds); } + virtual void setColumnInfo(KexiDB::QueryColumnInfo* cinfo); + + virtual void setInvalidState(const QString& text); + virtual bool isReadOnly() const; + virtual void setReadOnly( bool readOnly ); + + virtual QVariant value(); + virtual bool valueIsNull(); + virtual bool valueIsEmpty(); + virtual bool valueIsValid(); + virtual bool valueChanged(); + virtual void clear(); + + //! Reimpelmented to also install \a listenter for internal editor + virtual void installListener(KexiDataItemChangesListener* listener); + + WidgetType widgetType() const; + void setWidgetType(WidgetType type); + + LabelPosition labelPosition() const; + virtual void setLabelPosition(LabelPosition position); + + QString caption() const; + void setCaption(const QString &caption); + + bool hasAutoCaption() const; + void setAutoCaption(bool autoCaption); + + /*! If \a displayDefaultValue is true, the value set by KexiDataItemInterface::setValue() + is displayed in a special way. Used by KexiFormDataProvider::fillDataItems(). + \a widget is equal to 'this'. + Reimplemented after KexiFormDataItemInterface. */ + virtual void setDisplayDefaultValue(QWidget* widget, bool displayDefaultValue); + + QWidget* editor() const; + QLabel* label() const; + + virtual bool cursorAtStart(); + virtual bool cursorAtEnd(); + + static WidgetType widgetTypeForFieldType(KexiDB::Field::Type type); + + /*! On design time it is not possible to pass a reference to KexiDB::Field object + so we're just providing field type. Only used when widget type is Auto. + @internal */ + void setFieldTypeInternal(int kexiDBFieldType); + + /*! On design time it is not possible to pass a reference to KexiDB::Field object + so we're just providing field caption. Only used when widget type is Auto. + @internal */ + void setFieldCaptionInternal(const QString& text); + + /*! @internal */ + int fieldTypeInternal() const; + + /*! @internal */ + QString fieldCaptionInternal() const; + + virtual QSize sizeHint() const; + virtual void setFocusPolicy ( FocusPolicy policy ); + + //! Reimplemented to return internal editor's color. + const QColor & paletteForegroundColor() const; + + //! Reimplemented to set internal editor's color. + void setPaletteForegroundColor( const QColor & color ); + + //! Reimplemented to return internal editor's color. + const QColor & paletteBackgroundColor() const; + + //! Reimplemented to set internal editor's color. + virtual void setPaletteBackgroundColor( const QColor & color ); + + //! \return label's foreground color + const QColor & foregroundLabelColor() const; + + //! Sets label's foreground color + virtual void setForegroundLabelColor( const QColor & color ); + + //! \return label's background color + const QColor & backgroundLabelColor() const; + + //! Sets label's background color + virtual void setBackgroundLabelColor( const QColor & color ); + + //! Reimplemented to accept subproperties. @see KFormDesigner::WidgetWithSubpropertiesInterface + virtual QVariant property( const char * name ) const; + + //! Reimplemented to accept subproperties. @see KFormDesigner::WidgetWithSubpropertiesInterface + virtual bool setProperty( const char * name, const QVariant & value ); + + /*! Called by the top-level form on key press event to consume widget-specific shortcuts. */ + virtual bool keyPressed(QKeyEvent *ke); + + public slots: + virtual void unsetPalette(); + + protected slots: +// void slotValueChanged(); + virtual void paletteChange( const QPalette& oldPal ); + + //! Implemented for KexiDataItemInterface + virtual void moveCursorToEnd(); + + //! Implemented for KexiDataItemInterface + virtual void moveCursorToStart(); + + //! Implemented for KexiDataItemInterface + virtual void selectAll(); + + protected: + virtual void setValueInternal(const QVariant&add, bool removeOld); + void init(const QString &text, WidgetType type, LabelPosition pos); + virtual void createEditor(); + void changeText(const QString &text, bool beautify = true); +// virtual void paintEvent( QPaintEvent* pe ); + void updateInformationAboutUnboundField(); + + //! internal editor can be created too late, so certain properties should be copied + void copyPropertiesToEditor(); + + virtual bool eventFilter( QObject *o, QEvent *e ); + + //! Used by @ref setLabelPositionInternal(LabelPosition) + void setLabelPositionInternal(LabelPosition position, bool noLabel); + + //! Used by KexiDBAutoField::setColumnInfo() and KexiDBComboBox::setColumnInfo() + void setColumnInfoInternal(KexiDB::QueryColumnInfo* cinfo, KexiDB::QueryColumnInfo* visibleColumnInfo); + + private: + class Private; + Private *d; +}; + +#endif diff --git a/kexi/plugins/forms/widgets/kexidbcheckbox.cpp b/kexi/plugins/forms/widgets/kexidbcheckbox.cpp new file mode 100644 index 00000000..6b63851a --- /dev/null +++ b/kexi/plugins/forms/widgets/kexidbcheckbox.cpp @@ -0,0 +1,175 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr> + 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 "kexidbcheckbox.h" + +#include <kexiutils/utils.h> +#include <kexidb/queryschema.h> + +KexiDBCheckBox::KexiDBCheckBox(const QString &text, QWidget *parent, const char *name) + : QCheckBox(text, parent, name), KexiFormDataItemInterface() + , m_invalidState(false) + , m_tristateChanged(false) + , m_tristate(TristateDefault) +{ + setFocusPolicy(QWidget::StrongFocus); + updateTristate(); + connect(this, SIGNAL(stateChanged(int)), this, SLOT(slotStateChanged(int))); +} + +KexiDBCheckBox::~KexiDBCheckBox() +{ +} + +void KexiDBCheckBox::setInvalidState( const QString& displayText ) +{ + setEnabled(false); + setState(NoChange); + m_invalidState = true; +//! @todo move this to KexiDataItemInterface::setInvalidStateInternal() ? + if (focusPolicy() & TabFocus) + setFocusPolicy(QWidget::ClickFocus); + setText(displayText); +} + +void +KexiDBCheckBox::setEnabled(bool enabled) +{ + if(enabled && m_invalidState) + return; + QCheckBox::setEnabled(enabled); +} + +void +KexiDBCheckBox::setReadOnly(bool readOnly) +{ + setEnabled(!readOnly); +} + +void KexiDBCheckBox::setValueInternal(const QVariant &add, bool removeOld) +{ + Q_UNUSED(add); + Q_UNUSED(removeOld); + if (isTristateInternal()) + setState( m_origValue.isNull() ? NoChange : (m_origValue.toBool() ? On : Off) ); + else + setState( m_origValue.toBool() ? On : Off ); +} + +QVariant +KexiDBCheckBox::value() +{ + if (state()==NoChange) + return QVariant(); + return QVariant(state()==On, 1); +} + +void KexiDBCheckBox::slotStateChanged(int ) +{ + signalValueChanged(); +} + +bool KexiDBCheckBox::valueIsNull() +{ + return state() == NoChange; +} + +bool KexiDBCheckBox::valueIsEmpty() +{ + return false; +} + +bool KexiDBCheckBox::isReadOnly() const +{ + return !isEnabled(); +} + +QWidget* +KexiDBCheckBox::widget() +{ + return this; +} + +bool KexiDBCheckBox::cursorAtStart() +{ + return false; //! \todo ? +} + +bool KexiDBCheckBox::cursorAtEnd() +{ + return false; //! \todo ? +} + +void KexiDBCheckBox::clear() +{ + setState(NoChange); +} + +void KexiDBCheckBox::setTristate(KexiDBCheckBox::Tristate tristate) +{ + m_tristateChanged = true; + m_tristate = tristate; + updateTristate(); +} + +KexiDBCheckBox::Tristate KexiDBCheckBox::isTristate() const +{ + return m_tristate; +} + +bool KexiDBCheckBox::isTristateInternal() const +{ + if (m_tristate == TristateDefault) + return !dataSource().isEmpty(); + + return m_tristate == TristateOn; +} + +void KexiDBCheckBox::updateTristate() +{ + if (m_tristate == TristateDefault) { +//! @todo the data source may be defined as NOT NULL... thus disallowing NULL state + QCheckBox::setTristate( !dataSource().isEmpty() ); + } + else { + QCheckBox::setTristate( m_tristate == TristateOn ); + } +} + +void KexiDBCheckBox::setDataSource(const QString &ds) +{ + KexiFormDataItemInterface::setDataSource(ds); + updateTristate(); +} + +void KexiDBCheckBox::setDisplayDefaultValue(QWidget *widget, bool displayDefaultValue) +{ + KexiFormDataItemInterface::setDisplayDefaultValue(widget, displayDefaultValue); + // initialize display parameters for default / entered value + KexiDisplayUtils::DisplayParameters * const params + = displayDefaultValue ? m_displayParametersForDefaultValue : m_displayParametersForEnteredValue; +// setFont(params->font); + QPalette pal(palette()); +// pal.setColor(QPalette::Active, QColorGroup::Text, params->textColor); + pal.setColor(QPalette::Active, QColorGroup::Foreground, params->textColor); + setPalette(pal); +} + +#include "kexidbcheckbox.moc" diff --git a/kexi/plugins/forms/widgets/kexidbcheckbox.h b/kexi/plugins/forms/widgets/kexidbcheckbox.h new file mode 100644 index 00000000..d4a68bf3 --- /dev/null +++ b/kexi/plugins/forms/widgets/kexidbcheckbox.h @@ -0,0 +1,99 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr> + 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 KexiDBCheckBox_H +#define KexiDBCheckBox_H + +#include "kexiformdataiteminterface.h" +#include <qcheckbox.h> + +//! @short A db-aware check box +class KEXIFORMUTILS_EXPORT KexiDBCheckBox : public QCheckBox, public KexiFormDataItemInterface +{ + Q_OBJECT + Q_PROPERTY(QString dataSource READ dataSource WRITE setDataSource DESIGNABLE true) + Q_PROPERTY(QCString dataSourceMimeType READ dataSourceMimeType WRITE setDataSourceMimeType DESIGNABLE true) + Q_OVERRIDE( Tristate tristate READ isTristate WRITE setTristate ) + Q_ENUMS( Tristate ) + + public: + KexiDBCheckBox(const QString &text, QWidget *parent, const char *name=0); + virtual ~KexiDBCheckBox(); + + inline QString dataSource() const { return KexiFormDataItemInterface::dataSource(); } + inline QCString dataSourceMimeType() const { return KexiFormDataItemInterface::dataSourceMimeType(); } + virtual QVariant value(); + virtual void setInvalidState( const QString& displayText ); + + //! \return true if editor's value is null (not empty) + //! Used for checking if a given constraint within table of form is met. + virtual bool valueIsNull(); + + //! \return true if editor's value is empty (not necessary null). + //! Only few data types can accept "EMPTY" property + //! (use KexiDB::Field::hasEmptyProperty() to check this). + //! Used for checking if a given constraint within table or form is met. + virtual bool valueIsEmpty(); + + /*! \return 'readOnly' flag for this widget. */ + virtual bool isReadOnly() const; + + /*! \return the view widget of this item, e.g. line edit widget. */ + virtual QWidget* widget(); + + virtual bool cursorAtStart(); + virtual bool cursorAtEnd(); + virtual void clear(); + + virtual void setEnabled(bool enabled); + + enum Tristate { TristateDefault, TristateOn, TristateOff }; + + void setTristate(Tristate tristate); + Tristate isTristate() const; + + /*! Reimplemented after KexiFormDataItemInterface. */ + virtual void setDisplayDefaultValue(QWidget* widget, bool displayDefaultValue); + + public slots: + void setDataSource(const QString &ds); + inline void setDataSourceMimeType(const QCString &ds) { KexiFormDataItemInterface::setDataSourceMimeType(ds); } + void slotStateChanged(int state); + + //! This implementation just disables read only widget + virtual void setReadOnly( bool readOnly ); + + protected: + virtual void setValueInternal(const QVariant& add, bool removeOld); + + //! \return true in isTristate() == TristateDefault and the widget has bound data source + //! or if isTristate() == TristateOn, else false is returned. + bool isTristateInternal() const; + + //! Updates tristate in QCheckBox itself according to m_tristate. + void updateTristate(); + + private: + bool m_invalidState : 1; + bool m_tristateChanged : 1; //!< used in setTristate() + Tristate m_tristate; //!< used in isTristate() and setTristate() +}; + +#endif diff --git a/kexi/plugins/forms/widgets/kexidbcombobox.cpp b/kexi/plugins/forms/widgets/kexidbcombobox.cpp new file mode 100644 index 00000000..19366a15 --- /dev/null +++ b/kexi/plugins/forms/widgets/kexidbcombobox.cpp @@ -0,0 +1,550 @@ +/* This file is part of the KDE project + Copyright (C) 2006-2007 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 "kexidbcombobox.h" +#include "kexidblineedit.h" +#include "../kexiformscrollview.h" + +#include <kcombobox.h> +#include <kdebug.h> +#include <kapplication.h> + +#include <qmetaobject.h> +#include <qpainter.h> +#include <qstyle.h> +#include <qdrawutil.h> +#include <qptrdict.h> +#include <qcursor.h> + +#include <kexidb/queryschema.h> +#include <widget/tableview/kexicomboboxpopup.h> +#include <widget/tableview/kexicelleditorfactory.h> +#include <kexiutils/utils.h> + +//! @internal +class KexiDBComboBox::Private +{ + public: + Private() + : popup(0) + , visibleColumnInfo(0) + , subWidgetsWithDisabledEvents(0) + , isEditable(false) + , buttonPressed(false) + , mouseOver(false) + , dataEnteredByHand(true) + { + } + ~Private() + { + delete subWidgetsWithDisabledEvents; + subWidgetsWithDisabledEvents = 0; + } + + KexiComboBoxPopup *popup; + KComboBox *paintedCombo; //!< fake combo used only to pass it as 'this' for QStyle (because styles use <static_cast>) + QSize sizeHint; //!< A cache for KexiDBComboBox::sizeHint(), + //!< rebuilt by KexiDBComboBox::fontChange() and KexiDBComboBox::styleChange() + KexiDB::QueryColumnInfo* visibleColumnInfo; + QPtrDict<char> *subWidgetsWithDisabledEvents; //! used to collect subwidget and its children (if isEditable is false) + bool isEditable : 1; //!< true is the combo box is editable + bool buttonPressed : 1; + bool mouseOver : 1; + bool dataEnteredByHand : 1; + bool designMode : 1; +}; + +//------------------------------------- + +KexiDBComboBox::KexiDBComboBox(QWidget *parent, const char *name, bool designMode) + : KexiDBAutoField(parent, name, designMode, NoLabel) + , KexiComboBoxBase() + , d(new Private()) +{ + setMouseTracking(true); + setFocusPolicy(WheelFocus); + installEventFilter(this); + d->designMode = designMode; + d->paintedCombo = new KComboBox(this); + d->paintedCombo->hide(); + d->paintedCombo->move(0,0); +} + +KexiDBComboBox::~KexiDBComboBox() +{ + delete d; +} + +KexiComboBoxPopup *KexiDBComboBox::popup() const +{ + return d->popup; +} + +void KexiDBComboBox::setPopup(KexiComboBoxPopup *popup) +{ + d->popup = popup; +} + +void KexiDBComboBox::setEditable(bool set) +{ + if (d->isEditable == set) + return; + d->isEditable = set; + d->paintedCombo->setEditable(set); + if (set) + createEditor(); + else { + delete m_subwidget; + m_subwidget = 0; + } + update(); +} + +bool KexiDBComboBox::isEditable() const +{ + return d->isEditable; +} + +void KexiDBComboBox::paintEvent( QPaintEvent * ) +{ + QPainter p( this ); + QColorGroup cg( palette().active() ); +// if ( hasFocus() ) +// cg.setColor(QColorGroup::Base, cg.highlight()); +// else + cg.setColor(QColorGroup::Base, paletteBackgroundColor()); //update base color using (reimplemented) bg color + p.setPen(cg.text()); + + QStyle::SFlags flags = QStyle::Style_Default; + if (isEnabled()) + flags |= QStyle::Style_Enabled; + if (hasFocus()) + flags |= QStyle::Style_HasFocus; + if (d->mouseOver) + flags |= QStyle::Style_MouseOver; + + if ( width() < 5 || height() < 5 ) { + qDrawShadePanel( &p, rect(), cg, FALSE, 2, &cg.brush( QColorGroup::Button ) ); + return; + } + +//! @todo support reverse layout +//bool reverse = QApplication::reverseLayout(); + style().drawComplexControl( QStyle::CC_ComboBox, &p, d->paintedCombo /*this*/, rect(), cg, + flags, (uint)QStyle::SC_All, + (d->buttonPressed ? QStyle::SC_ComboBoxArrow : QStyle::SC_None ) + ); + + if (d->isEditable) { + //if editable, editor paints itself, nothing to do + } + else { //not editable: we need to paint the current item + QRect editorGeometry( this->editorGeometry() ); + if ( hasFocus() ) { + if (0==qstrcmp(style().name(), "windows")) //a hack + p.fillRect( editorGeometry, cg.brush( QColorGroup::Highlight ) ); + QRect r( QStyle::visualRect( style().subRect( QStyle::SR_ComboBoxFocusRect, d->paintedCombo ), this ) ); + r = QRect(r.left()-1, r.top()-1, r.width()+2, r.height()+2); //enlare by 1 pixel each side to avoid covering by the subwidget + style().drawPrimitive( QStyle::PE_FocusRect, &p, + r, cg, flags | QStyle::Style_FocusAtBorder, QStyleOption(cg.highlight())); + } + //todo + } +} + +QRect KexiDBComboBox::editorGeometry() const +{ + QRect r( QStyle::visualRect( + style().querySubControlMetrics(QStyle::CC_ComboBox, d->paintedCombo, + QStyle::SC_ComboBoxEditField), d->paintedCombo ) ); + + //if ((height()-r.bottom())<6) + // r.setBottom(height()-6); + return r; +} + +void KexiDBComboBox::createEditor() +{ + KexiDBAutoField::createEditor(); + if (m_subwidget) { + m_subwidget->setGeometry( editorGeometry() ); + if (!d->isEditable) { + m_subwidget->setCursor(QCursor(Qt::ArrowCursor)); // widgets like listedit have IbeamCursor, we don't want that +//! @todo Qt4: set transparent background, for now we're setting button color + QPalette subwidgetPalette( m_subwidget->palette() ); + subwidgetPalette.setColor(QPalette::Active, QColorGroup::Base, + subwidgetPalette.color(QPalette::Active, QColorGroup::Button)); + m_subwidget->setPalette( subwidgetPalette ); + if (d->subWidgetsWithDisabledEvents) + d->subWidgetsWithDisabledEvents->clear(); + else + d->subWidgetsWithDisabledEvents = new QPtrDict<char>(); + d->subWidgetsWithDisabledEvents->insert(m_subwidget, (char*)1); + m_subwidget->installEventFilter(this); + QObjectList *l = m_subwidget->queryList( "QWidget" ); + for ( QObjectListIt it( *l ); it.current(); ++it ) { + d->subWidgetsWithDisabledEvents->insert(it.current(), (char*)1); + it.current()->installEventFilter(this); + } + delete l; + } + } + updateGeometry(); +} + +void KexiDBComboBox::setLabelPosition(LabelPosition position) +{ + if(m_subwidget) { + if (-1 != m_subwidget->metaObject()->findProperty("frameShape", true)) + m_subwidget->setProperty("frameShape", QVariant((int)QFrame::NoFrame)); + m_subwidget->setGeometry( editorGeometry() ); + } +// KexiSubwidgetInterface *subwidgetInterface = dynamic_cast<KexiSubwidgetInterface*>((QWidget*)m_subwidget); + // update size policy +// if (subwidgetInterface && subwidgetInterface->subwidgetStretchRequired(this)) { + QSizePolicy sizePolicy( this->sizePolicy() ); + if(position == Left) + sizePolicy.setHorData( QSizePolicy::Minimum ); + else + sizePolicy.setVerData( QSizePolicy::Minimum ); + //m_subwidget->setSizePolicy(sizePolicy); + setSizePolicy(sizePolicy); + //} +// } +} + +QRect KexiDBComboBox::buttonGeometry() const +{ + QRect arrowRect( + style().querySubControlMetrics( QStyle::CC_ComboBox, d->paintedCombo, QStyle::SC_ComboBoxArrow) ); + arrowRect = QStyle::visualRect(arrowRect, d->paintedCombo); + arrowRect.setHeight( QMAX( height() - (2 * arrowRect.y()), arrowRect.height() ) ); // a fix for Motif style + return arrowRect; +} + +bool KexiDBComboBox::handleMousePressEvent(QMouseEvent *e) +{ + if ( e->button() != Qt::LeftButton || d->designMode ) + return true; +/*todo if ( m_discardNextMousePress ) { + d->discardNextMousePress = FALSE; + return; + }*/ + + if ( /*count() &&*/ ( !isEditable() || buttonGeometry().contains( e->pos() ) ) ) { + d->buttonPressed = false; + +/* if ( d->usingListBox() ) { + listBox()->blockSignals( TRUE ); + qApp->sendEvent( listBox(), e ); // trigger the listbox's autoscroll + listBox()->setCurrentItem(d->current); + listBox()->blockSignals( FALSE ); + popup(); + if ( arrowRect.contains( e->pos() ) ) { + d->arrowPressed = TRUE; + d->arrowDown = TRUE; + repaint( FALSE ); + } + } else {*/ + showPopup(); + return true; + } + return false; +} + +bool KexiDBComboBox::handleKeyPressEvent(QKeyEvent *ke) +{ + const int k = ke->key(); + const bool dropDown = (ke->state() == Qt::NoButton && ((k==Qt::Key_F2 && !d->isEditable) || k==Qt::Key_F4)) + || (ke->state() == Qt::AltButton && k==Qt::Key_Down); + const bool escPressed = ke->state() == Qt::NoButton && k==Qt::Key_Escape; + const bool popupVisible = popup() && popup()->isVisible(); + if ((dropDown || escPressed) && popupVisible) { + popup()->hide(); + return true; + } + else if (dropDown && !popupVisible) { + d->buttonPressed = false; + showPopup(); + return true; + } + else if (popupVisible) { + const bool enterPressed = k==Qt::Key_Enter || k==Qt::Key_Return; + if (enterPressed/* && m_internalEditorValueChanged*/) { + acceptPopupSelection(); + return true; + } + return handleKeyPressForPopup( ke ); + } + + return false; +} + +bool KexiDBComboBox::keyPressed(QKeyEvent *ke) +{ + if (KexiDBAutoField::keyPressed(ke)) + return true; + + const int k = ke->key(); + const bool popupVisible = popup() && popup()->isVisible(); + const bool escPressed = ke->state() == Qt::NoButton && k==Qt::Key_Escape; + if (escPressed && popupVisible) { + popup()->hide(); + return true; + } + if (ke->state() == Qt::NoButton && (k==Qt::Key_PageDown || k==Qt::Key_PageUp) && popupVisible) + return true; + return false; +} + +void KexiDBComboBox::mousePressEvent( QMouseEvent *e ) +{ + if (handleMousePressEvent(e)) + return; + +// QTimer::singleShot( 200, this, SLOT(internalClickTimeout())); +// d->shortClick = TRUE; +// } + KexiDBAutoField::mousePressEvent( e ); +} + +void KexiDBComboBox::mouseDoubleClickEvent( QMouseEvent *e ) +{ + mousePressEvent( e ); +} + +bool KexiDBComboBox::eventFilter( QObject *o, QEvent *e ) +{ + if (o==this) { + if (e->type()==QEvent::Resize) { + d->paintedCombo->resize(size()); + if (m_subwidget) + m_subwidget->setGeometry( editorGeometry() ); + } + else if (e->type()==QEvent::Enter) { + if (!d->isEditable + || /*over button if editable combo*/buttonGeometry().contains( static_cast<QMouseEvent*>(e)->pos() )) + { + d->mouseOver = true; + update(); + } + } + else if (e->type()==QEvent::MouseMove) { + if (d->isEditable) { + const bool overButton = buttonGeometry().contains( static_cast<QMouseEvent*>(e)->pos() ); + if (overButton != d->mouseOver) { + d->mouseOver = overButton; + update(); + } + } + } + else if (e->type()==QEvent::Leave) { + d->mouseOver = false; + update(); + } + else if (e->type()==QEvent::KeyPress) { + // handle F2/F4 + if (handleKeyPressEvent(static_cast<QKeyEvent*>(e))) + return true; + } + else if (e->type()==QEvent::FocusOut) { + if (popup() && popup()->isVisible()) { + popup()->hide(); + undoChanges(); + } + } + } + else if (!d->isEditable && d->subWidgetsWithDisabledEvents && d->subWidgetsWithDisabledEvents->find(o)) { + if (e->type()==QEvent::MouseButtonPress) { + // clicking the subwidget should mean the same as clicking the combo box (i.e. show the popup) + if (handleMousePressEvent(static_cast<QMouseEvent*>(e))) + return true; + } + else if (e->type()==QEvent::KeyPress) { + if (handleKeyPressEvent(static_cast<QKeyEvent*>(e))) + return true; + } + return e->type()!=QEvent::Paint; + } + return KexiDBAutoField::eventFilter( o, e ); +} + +bool KexiDBComboBox::subwidgetStretchRequired(KexiDBAutoField* autoField) const +{ + Q_UNUSED(autoField); + return true; +} + +void KexiDBComboBox::setPaletteBackgroundColor( const QColor & color ) +{ + KexiDBAutoField::setPaletteBackgroundColor(color); + QPalette pal(palette()); + QColorGroup cg(pal.active()); + pal.setColor(QColorGroup::Base, red); + pal.setColor(QColorGroup::Background, red); + pal.setActive(cg); + QWidget::setPalette(pal); + update(); +} + +bool KexiDBComboBox::valueChanged() +{ + kdDebug() << "KexiDataItemInterface::valueChanged(): " << m_origValue.toString() << " ? " << value().toString() << endl; + return m_origValue != value(); +} + +void +KexiDBComboBox::setColumnInfo(KexiDB::QueryColumnInfo* cinfo) +{ + KexiFormDataItemInterface::setColumnInfo(cinfo); +} + +void KexiDBComboBox::setVisibleColumnInfo(KexiDB::QueryColumnInfo* cinfo) +{ + d->visibleColumnInfo = cinfo; + // we're assuming we already have columnInfo() + setColumnInfoInternal(columnInfo(), d->visibleColumnInfo); +} + +KexiDB::QueryColumnInfo* KexiDBComboBox::visibleColumnInfo() const +{ + return d->visibleColumnInfo; +} + +void KexiDBComboBox::moveCursorToEndInInternalEditor() +{ + if (d->isEditable && m_moveCursorToEndInInternalEditor_enabled) + moveCursorToEnd(); +} + +void KexiDBComboBox::selectAllInInternalEditor() +{ + if (d->isEditable && m_selectAllInInternalEditor_enabled) + selectAll(); +} + +void KexiDBComboBox::setValueInternal(const QVariant& add, bool removeOld) +{ + //// use KexiDBAutoField instead of KexiComboBoxBase::setValueInternal + //// expects existing popup(), but we want to have delayed creation + if (popup()) + popup()->hide(); + KexiComboBoxBase::setValueInternal(add, removeOld); +} + +void KexiDBComboBox::setVisibleValueInternal(const QVariant& value) +{ + KexiFormDataItemInterface *iface = dynamic_cast<KexiFormDataItemInterface*>((QWidget*)m_subwidget); + if(iface) + iface->setValue(value, QVariant(), false /*!removeOld*/); +} + +QVariant KexiDBComboBox::visibleValue() +{ + return KexiComboBoxBase::visibleValue(); +} + +void KexiDBComboBox::setValueInInternalEditor(const QVariant& value) +{ + if (!m_setValueInInternalEditor_enabled) + return; + KexiFormDataItemInterface *iface = dynamic_cast<KexiFormDataItemInterface*>((QWidget*)m_subwidget); + if(iface) + iface->setValue(value, QVariant(), false/*!removeOld*/); +} + +QVariant KexiDBComboBox::valueFromInternalEditor() +{ + return KexiDBAutoField::value(); +} + +QPoint KexiDBComboBox::mapFromParentToGlobal(const QPoint& pos) const +{ +// const KexiFormScrollView* view = KexiUtils::findParentConst<const KexiFormScrollView>(this, "KexiFormScrollView"); + if (!parentWidget()) + return QPoint(-1,-1); + return parentWidget()->mapToGlobal(pos); +// return view->viewport()->mapToGlobal(pos); +} + +int KexiDBComboBox::popupWidthHint() const +{ + return width(); //popup() ? popup()->width() : 0; +} + +void KexiDBComboBox::fontChange( const QFont & oldFont ) +{ + d->sizeHint = QSize(); //force rebuild the cache + KexiDBAutoField::fontChange(oldFont); +} + +void KexiDBComboBox::styleChange( QStyle& oldStyle ) +{ + KexiDBAutoField::styleChange( oldStyle ); + d->sizeHint = QSize(); //force rebuild the cache + if (m_subwidget) + m_subwidget->setGeometry( editorGeometry() ); +} + +QSize KexiDBComboBox::sizeHint() const +{ + if ( isVisible() && d->sizeHint.isValid() ) + return d->sizeHint; + + const int maxWidth = 7 * fontMetrics().width(QChar('x')) + 18; + const int maxHeight = QMAX( fontMetrics().lineSpacing(), 14 ) + 2; + d->sizeHint = (style().sizeFromContents(QStyle::CT_ComboBox, d->paintedCombo, + QSize(maxWidth, maxHeight)).expandedTo(QApplication::globalStrut())); + + return d->sizeHint; +} + +void KexiDBComboBox::editRequested() +{ +} + +void KexiDBComboBox::acceptRequested() +{ + signalValueChanged(); +} + +void KexiDBComboBox::slotRowAccepted(KexiTableItem *item, int row) +{ + d->dataEnteredByHand = false; + KexiComboBoxBase::slotRowAccepted(item, row); + d->dataEnteredByHand = true; +} + +void KexiDBComboBox::beforeSignalValueChanged() +{ + if (d->dataEnteredByHand) { + KexiFormDataItemInterface *iface = dynamic_cast<KexiFormDataItemInterface*>((QWidget*)m_subwidget); + if (iface) { + slotInternalEditorValueChanged( iface->value() ); + } + } +} + +void KexiDBComboBox::undoChanges() +{ + KexiDBAutoField::undoChanges(); + KexiComboBoxBase::undoChanges(); +} + +#include "kexidbcombobox.moc" diff --git a/kexi/plugins/forms/widgets/kexidbcombobox.h b/kexi/plugins/forms/widgets/kexidbcombobox.h new file mode 100644 index 00000000..5208d37d --- /dev/null +++ b/kexi/plugins/forms/widgets/kexidbcombobox.h @@ -0,0 +1,181 @@ +/* This file is part of the KDE project + Copyright (C) 2006-2007 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 KexiDBComboBox_H +#define KexiDBComboBox_H + +#include "kexidbutils.h" +#include "kexidbautofield.h" +#include <widget/tableview/kexicomboboxbase.h> + +//! @short Combo box widget for Kexi forms +/*! This widget is implemented on top of KexiDBAutoField, + so as it uses KexiDBAutoField's ability of embedding subwidgets, + it can display not only a line edit but also text edit or image box + (more can be added in the future). + A drop-down button is added to mimic native combo box widget's functionality. +*/ +class KEXIFORMUTILS_EXPORT KexiDBComboBox : + public KexiDBAutoField, public KexiComboBoxBase +{ + Q_OBJECT + Q_PROPERTY( bool editable READ isEditable WRITE setEditable ) + //properties from KexiDBAutoField that should not be visible: + Q_OVERRIDE(QColor paletteBackgroundColor READ paletteBackgroundColor WRITE setPaletteBackgroundColor DESIGNABLE true RESET unsetPalette) + Q_OVERRIDE(QColor foregroundLabelColor DESIGNABLE false) + Q_OVERRIDE(QColor backgroundLabelColor DESIGNABLE false) + Q_OVERRIDE(bool autoCaption DESIGNABLE false) + + public: + KexiDBComboBox(QWidget *parent, const char *name=0, bool designMode = true); + virtual ~KexiDBComboBox(); + + //! Implemented for KexiComboBoxBase: form has no 'related data' model (only the full database model) + virtual KexiTableViewColumn *column() const { return 0; } + + //! Implemented for KexiComboBoxBase + virtual KexiDB::Field *field() const { return KexiDBAutoField::field(); } + + //! Implemented for KexiComboBoxBase + virtual QVariant origValue() const { return m_origValue; } + + void setEditable(bool set); + bool isEditable() const; + + virtual void setLabelPosition(LabelPosition position); + + virtual QVariant value() { return KexiComboBoxBase::value(); } + + virtual QVariant visibleValue(); + + //! Reimpemented because to avoid taking value from the internal editor (index is taken from the popup instead) + virtual bool valueChanged(); + + virtual QSize sizeHint() const; + + //! Reimplemented after KexiDBAutoField: jsut sets \a cinfo without initializing a subwidget. + //! Initialization is performed by \ref setVisibleColumnInfo(). + virtual void setColumnInfo(KexiDB::QueryColumnInfo* cinfo); + + /*! Used internally to set visible database column information. + Reimplemented: performs initialization of the subwidget. */ + virtual void setVisibleColumnInfo(KexiDB::QueryColumnInfo* cinfo); + + /*! \return visible database column information for this item. + Reimplemented. */ + virtual KexiDB::QueryColumnInfo* visibleColumnInfo() const; + + const QColor & paletteBackgroundColor() const { return KexiDBAutoField::paletteBackgroundColor(); } + + //! Reimplemented to also set 'this' widget's background color, not only subwidget's. + virtual void setPaletteBackgroundColor( const QColor & color ); + + /*! Undoes changes made to this item - just resets the widget to original value. + Reimplemented after KexiFormDataItemInterface to also revert the visible value + (i.e. text) to the original state. */ + virtual void undoChanges(); + + public slots: + void slotRowAccepted(KexiTableItem *item, int row); + void slotItemSelected(KexiTableItem* item) { KexiComboBoxBase::slotItemSelected(item); } + + protected slots: + void slotInternalEditorValueChanged(const QVariant& v) + { KexiComboBoxBase::slotInternalEditorValueChanged(v); } + + protected: + QRect buttonGeometry() const; + + virtual void paintEvent( QPaintEvent * ); + + virtual void mousePressEvent( QMouseEvent *e ); + + void mouseDoubleClickEvent( QMouseEvent *e ); + + virtual bool eventFilter( QObject *o, QEvent *e ); + + //! \return internal editor's geometry + QRect editorGeometry() const; + + //! Creates editor. Reimplemented, because if the combo box is not editable, + //! editor should not be created. + virtual void createEditor(); + + /*! Reimplemented */ + virtual void styleChange( QStyle& oldStyle ); + + /*! Reimplemented */ + virtual void fontChange( const QFont & oldFont ); + + virtual bool subwidgetStretchRequired(KexiDBAutoField* autoField) const; + + //! Implemented for KexiComboBoxBase + virtual QWidget *internalEditor() const { return /*WidgetWithSubpropertiesInterface*/m_subwidget; } + + //! Implemented for KexiComboBoxBase. Does nothing if the widget is not editable. + virtual void moveCursorToEndInInternalEditor(); + + //! Implemented for KexiComboBoxBase. Does nothing if the widget is not editable. + virtual void selectAllInInternalEditor(); + + //! Implemented for KexiComboBoxBase + virtual void setValueInInternalEditor(const QVariant& value); + + //! Implemented for KexiComboBoxBase + virtual QVariant valueFromInternalEditor(); + + //! Implemented for KexiComboBoxBase + virtual void editRequested(); + + //! Implemented for KexiComboBoxBase + virtual void acceptRequested(); + + //! Implement this to return a position \a pos mapped from parent (e.g. viewport) + //! to global coordinates. QPoint(-1, -1) should be returned if this cannot be computed. + virtual QPoint mapFromParentToGlobal(const QPoint& pos) const; + + //! Implement this to return a hint for popup width. + virtual int popupWidthHint() const; + + virtual void setValueInternal(const QVariant& add, bool removeOld); + + //! Implemented to handle visible value instead of index + virtual void setVisibleValueInternal(const QVariant& value); + + bool handleMousePressEvent(QMouseEvent *e); + + bool handleKeyPressEvent(QKeyEvent *ke); + + //! Implemented for KexiDataItemInterface + virtual void beforeSignalValueChanged(); + + virtual KexiComboBoxPopup *popup() const; + virtual void setPopup(KexiComboBoxPopup *popup); + + /*! Called by top-level form on key press event. + Used for Key_Escape to if the popup is visible, + so the key press won't be consumed to perform "cancel editing". + Also used for grabbing page down/up keys. */ + virtual bool keyPressed(QKeyEvent *ke); + + class Private; + Private * const d; +}; + +#endif diff --git a/kexi/plugins/forms/widgets/kexidbdateedit.cpp b/kexi/plugins/forms/widgets/kexidbdateedit.cpp new file mode 100644 index 00000000..32584fce --- /dev/null +++ b/kexi/plugins/forms/widgets/kexidbdateedit.cpp @@ -0,0 +1,230 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr> + Copyright (C) 2004-2005 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 "kexidbdateedit.h" +#include <qlayout.h> +#include <qtoolbutton.h> +#include <kpopupmenu.h> +#include <kdatepicker.h> +#include <kdatetbl.h> + +#include <kexiutils/utils.h> +#include <kexidb/queryschema.h> + +KexiDBDateEdit::KexiDBDateEdit(const QDate &date, QWidget *parent, const char *name) + : QWidget(parent, name), KexiFormDataItemInterface() +{ + m_invalidState = false; + m_cleared = false; + m_readOnly = false; + + m_edit = new QDateEdit(date, this); + m_edit->setAutoAdvance(true); + m_edit->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::MinimumExpanding); + connect( m_edit, SIGNAL(valueChanged(const QDate&)), this, SLOT(slotValueChanged(const QDate&)) ); + connect( m_edit, SIGNAL(valueChanged(const QDate&)), this, SIGNAL(dateChanged(const QDate&)) ); + + QToolButton* btn = new QToolButton(this); + btn->setText("..."); + btn->setFixedWidth( QFontMetrics(btn->font()).width(" ... ") ); + btn->setPopupDelay(1); //1 ms + +#ifdef QDateTimeEditor_HACK + m_dte_date = KexiUtils::findFirstChild<QDateTimeEditor>(m_edit, "QDateTimeEditor"); +#else + m_dte_date = 0; +#endif + + m_datePickerPopupMenu = new KPopupMenu(0, "date_popup"); + connect(m_datePickerPopupMenu, SIGNAL(aboutToShow()), this, SLOT(slotShowDatePicker())); + m_datePicker = new KDatePicker(m_datePickerPopupMenu, QDate::currentDate(), 0); + + KDateTable *dt = KexiUtils::findFirstChild<KDateTable>(m_datePicker, "KDateTable"); + if (dt) + connect(dt, SIGNAL(tableClicked()), this, SLOT(acceptDate())); + m_datePicker->setCloseButton(true); + m_datePicker->installEventFilter(this); + m_datePickerPopupMenu->insertItem(m_datePicker); + btn->setPopup(m_datePickerPopupMenu); + + QHBoxLayout* layout = new QHBoxLayout(this); + layout->addWidget(m_edit, 1); + layout->addWidget(btn, 0); + + setFocusProxy(m_edit); +} + +KexiDBDateEdit::~KexiDBDateEdit() +{ +} + +void KexiDBDateEdit::setInvalidState( const QString& ) +{ + setEnabled(false); + setReadOnly(true); + m_invalidState = true; +//! @todo move this to KexiDataItemInterface::setInvalidStateInternal() ? + if (focusPolicy() & TabFocus) + setFocusPolicy(QWidget::ClickFocus); +} + +void +KexiDBDateEdit::setEnabled(bool enabled) +{ + // prevent the user from reenabling the widget when it is in invalid state + if(enabled && m_invalidState) + return; + QWidget::setEnabled(enabled); +} + +void KexiDBDateEdit::setValueInternal(const QVariant &add, bool removeOld) +{ + int setNumberOnFocus = -1; + QDate d; + QString addString(add.toString()); + if (removeOld) { + if (!addString.isEmpty() && addString[0].latin1()>='0' && addString[0].latin1() <='9') { + setNumberOnFocus = addString[0].latin1()-'0'; + d = QDate(setNumberOnFocus*1000, 1, 1); + } + } + else + d = m_origValue.toDate(); + + m_edit->setDate(d); +} + +QVariant +KexiDBDateEdit::value() +{ + return QVariant(m_edit->date()); +} + +bool KexiDBDateEdit::valueIsNull() +{ + return !m_edit->date().isValid() || m_edit->date().isNull(); +} + +bool KexiDBDateEdit::valueIsEmpty() +{ + return m_cleared; +} + +bool KexiDBDateEdit::isReadOnly() const +{ + //! @todo: data/time edit API has no readonly flag, + //! so use event filter to avoid changes made by keyboard or mouse when m_readOnly==true + return m_readOnly; //!isEnabled(); +} + +void KexiDBDateEdit::setReadOnly(bool set) +{ + m_readOnly = set; +} + +QWidget* +KexiDBDateEdit::widget() +{ + return this; +} + +bool KexiDBDateEdit::cursorAtStart() +{ +#ifdef QDateTimeEditor_HACK + return m_dte_date && m_edit->hasFocus() && m_dte_date->focusSection()==0; +#else + return false; +#endif +} + +bool KexiDBDateEdit::cursorAtEnd() +{ +#ifdef QDateTimeEditor_HACK + return m_dte_date && m_edit->hasFocus() + && m_dte_date->focusSection()==int(m_dte_date->sectionCount()-1); +#else + return false; +#endif +} + +void KexiDBDateEdit::clear() +{ + m_edit->setDate(QDate()); + m_cleared = true; +} + +void +KexiDBDateEdit::slotValueChanged(const QDate&) +{ + m_cleared = false; +} + +void +KexiDBDateEdit::slotShowDatePicker() +{ + QDate date = m_edit->date(); + + m_datePicker->setDate(date); + m_datePicker->setFocus(); + m_datePicker->show(); + m_datePicker->setFocus(); +} + +void +KexiDBDateEdit::acceptDate() +{ + m_edit->setDate(m_datePicker->date()); + m_datePickerPopupMenu->hide(); +} + +bool +KexiDBDateEdit::eventFilter(QObject *o, QEvent *e) +{ + if (o != m_datePicker) + return false; + + switch (e->type()) { + case QEvent::Hide: + m_datePickerPopupMenu->hide(); + break; + case QEvent::KeyPress: + case QEvent::KeyRelease: { + QKeyEvent *ke = (QKeyEvent *)e; + if (ke->key()==Qt::Key_Enter || ke->key()==Qt::Key_Return) { + //accepting picker + acceptDate(); + return true; + } + else if (ke->key()==Qt::Key_Escape) { + //canceling picker + m_datePickerPopupMenu->hide(); + return true; + } + else + m_datePickerPopupMenu->setFocus(); + break; + } + default: + break; + } + return false; +} + +#include "kexidbdateedit.moc" diff --git a/kexi/plugins/forms/widgets/kexidbdateedit.h b/kexi/plugins/forms/widgets/kexidbdateedit.h new file mode 100644 index 00000000..2ad693a8 --- /dev/null +++ b/kexi/plugins/forms/widgets/kexidbdateedit.h @@ -0,0 +1,118 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr> + Copyright (C) 2004-2005 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 KexiDBDateEdit_H +#define KexiDBDateEdit_H + +#include "kexiformdataiteminterface.h" +#include <qdatetimeedit.h> + +class KPopupMenu; +class KDatePicker; +class QDateTimeEditor; + +//! @short A db-aware date editor +class KEXIFORMUTILS_EXPORT KexiDBDateEdit : public QWidget, public KexiFormDataItemInterface +{ + Q_OBJECT + Q_PROPERTY(QString dataSource READ dataSource WRITE setDataSource DESIGNABLE true) + Q_PROPERTY(QCString dataSourceMimeType READ dataSourceMimeType WRITE setDataSourceMimeType DESIGNABLE true) + // properties copied from QDateEdit + Q_ENUMS( Order ) + Q_PROPERTY( Order order READ order WRITE setOrder DESIGNABLE true) + Q_PROPERTY( QDate date READ date WRITE setDate DESIGNABLE true) + Q_PROPERTY( bool autoAdvance READ autoAdvance WRITE setAutoAdvance DESIGNABLE true) + Q_PROPERTY( QDate maxValue READ maxValue WRITE setMaxValue DESIGNABLE true) + Q_PROPERTY( QDate minValue READ minValue WRITE setMinValue DESIGNABLE true) + Q_PROPERTY( bool readOnly READ isReadOnly WRITE setReadOnly DESIGNABLE true ) + + public: + enum Order { DMY = QDateEdit::DMY, MDY = QDateEdit::MDY, YMD = QDateEdit::YMD, YDM = QDateEdit::YDM }; + + KexiDBDateEdit(const QDate &date, QWidget *parent, const char *name=0); + virtual ~KexiDBDateEdit(); + + inline QString dataSource() const { return KexiFormDataItemInterface::dataSource(); } + inline QCString dataSourceMimeType() const { return KexiFormDataItemInterface::dataSourceMimeType(); } + virtual QVariant value(); + virtual void setInvalidState( const QString& displayText ); + + //! \return true if editor's value is null (not empty) + //! Used for checking if a given constraint within table of form is met. + virtual bool valueIsNull(); + + //! \return true if editor's value is empty (not necessary null). + //! Only few data types can accept "EMPTY" property + //! (use KexiDB::Field::hasEmptyProperty() to check this). + //! Used for checking if a given constraint within table or form is met. + virtual bool valueIsEmpty(); + + /*! \return 'readOnly' flag for this widget. */ + virtual bool isReadOnly() const; + + /*! \return the view widget of this item, e.g. line edit widget. */ + virtual QWidget* widget(); + + virtual bool cursorAtStart(); + virtual bool cursorAtEnd(); + virtual void clear(); + + virtual void setEnabled(bool enabled); + + // property functions + inline QDate date() const { return m_edit->date(); } + inline void setOrder(Order order) { m_edit->setOrder( (QDateEdit::Order) order); } + inline Order order() const { return (Order)m_edit->order(); } + inline void setAutoAdvance( bool advance ) { m_edit->setAutoAdvance(advance); } + inline bool autoAdvance() const { return m_edit->autoAdvance(); } + inline void setMinValue(const QDate& d) { m_edit->setMinValue(d); } + inline QDate minValue() const { return m_edit->minValue(); } + inline void setMaxValue(const QDate& d) { m_edit->setMaxValue(d); } + inline QDate maxValue() const { return m_edit->maxValue(); } + + signals: + void dateChanged(const QDate &date); + + public slots: + inline void setDataSource(const QString &ds) { KexiFormDataItemInterface::setDataSource(ds); } + inline void setDataSourceMimeType(const QCString &ds) { KexiFormDataItemInterface::setDataSourceMimeType(ds); } + inline void setDate(const QDate& date) { m_edit->setDate(date); } + virtual void setReadOnly(bool set); + + protected slots: + void slotValueChanged(const QDate&); + void slotShowDatePicker(); + void acceptDate(); + + protected: + virtual void setValueInternal(const QVariant& add, bool removeOld); + virtual bool eventFilter(QObject *o, QEvent *e); + + private: + KDatePicker *m_datePicker; + QDateEdit *m_edit; + KPopupMenu *m_datePickerPopupMenu; + QDateTimeEditor *m_dte_date; + bool m_invalidState : 1; + bool m_cleared : 1; + bool m_readOnly : 1; +}; + +#endif diff --git a/kexi/plugins/forms/widgets/kexidbdatetimeedit.cpp b/kexi/plugins/forms/widgets/kexidbdatetimeedit.cpp new file mode 100644 index 00000000..faaeca66 --- /dev/null +++ b/kexi/plugins/forms/widgets/kexidbdatetimeedit.cpp @@ -0,0 +1,243 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr> + Copyright (C) 2004-2005 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 "kexidbdatetimeedit.h" + +#include <qtoolbutton.h> +#include <qlayout.h> +#include <kpopupmenu.h> +#include <kdatepicker.h> +#include <kdatetbl.h> +#include <kexiutils/utils.h> + +KexiDBDateTimeEdit::KexiDBDateTimeEdit(const QDateTime &datetime, QWidget *parent, const char *name) + : QWidget(parent, name), KexiFormDataItemInterface() +{ + m_invalidState = false; + m_cleared = false; + m_readOnly = false; + + m_dateEdit = new QDateEdit(datetime.date(), this); + m_dateEdit->setAutoAdvance(true); + m_dateEdit->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::MinimumExpanding); +// m_dateEdit->setFixedWidth( QFontMetrics(m_dateEdit->font()).width("8888-88-88___") ); + connect(m_dateEdit, SIGNAL(valueChanged(const QDate&)), this, SLOT(slotValueChanged())); + connect(m_dateEdit, SIGNAL(valueChanged(const QDate&)), this, SIGNAL(dateTimeChanged())); + + QToolButton* btn = new QToolButton(this); + btn->setText("..."); + btn->setFixedWidth( QFontMetrics(btn->font()).width(" ... ") ); + btn->setPopupDelay(1); //1 ms + + m_timeEdit = new QTimeEdit(datetime.time(), this);; + m_timeEdit->setAutoAdvance(true); + m_timeEdit->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::MinimumExpanding); + connect(m_timeEdit, SIGNAL(valueChanged(const QTime&)), this, SLOT(slotValueChanged())); + connect(m_timeEdit, SIGNAL(valueChanged(const QTime&)), this, SIGNAL(dateTimeChanged())); + +#ifdef QDateTimeEditor_HACK + m_dte_date = KexiUtils::findFirstChild<QDateTimeEditor>(m_dateEdit, "QDateTimeEditor"); + m_dte_time = KexiUtils::findFirstChild<QDateTimeEditor>(m_timeEdit, "QDateTimeEditor"); +#else + m_dte_date = 0; +#endif + + m_datePickerPopupMenu = new KPopupMenu(0, "date_popup"); + connect(m_datePickerPopupMenu, SIGNAL(aboutToShow()), this, SLOT(slotShowDatePicker())); + m_datePicker = new KDatePicker(m_datePickerPopupMenu, QDate::currentDate(), 0); + + KDateTable *dt = KexiUtils::findFirstChild<KDateTable>(m_datePicker, "KDateTable"); + if (dt) + connect(dt, SIGNAL(tableClicked()), this, SLOT(acceptDate())); + m_datePicker->setCloseButton(true); + m_datePicker->installEventFilter(this); + m_datePickerPopupMenu->insertItem(m_datePicker); + btn->setPopup(m_datePickerPopupMenu); + + QHBoxLayout* layout = new QHBoxLayout(this); + layout->addWidget(m_dateEdit, 0); + layout->addWidget(btn, 0); + layout->addWidget(m_timeEdit, 0); + //layout->addStretch(1); + + setFocusProxy(m_dateEdit); +} + +KexiDBDateTimeEdit::~KexiDBDateTimeEdit() +{ +} + +void KexiDBDateTimeEdit::setInvalidState(const QString & /*! @todo paint this text: text*/) +{ + setEnabled(false); + setReadOnly(true); + m_invalidState = true; +//! @todo move this to KexiDataItemInterface::setInvalidStateInternal() ? + if (focusPolicy() & TabFocus) + setFocusPolicy(QWidget::ClickFocus); +} + +void +KexiDBDateTimeEdit::setEnabled(bool enabled) +{ + // prevent the user from reenabling the widget when it is in invalid state + if(enabled && m_invalidState) + return; + QWidget::setEnabled(enabled); +} + +void KexiDBDateTimeEdit::setValueInternal(const QVariant &, bool ) +{ + m_dateEdit->setDate(m_origValue.toDate()); + m_timeEdit->setTime(m_origValue.toTime()); +} + +QVariant +KexiDBDateTimeEdit::value() +{ + return QDateTime(m_dateEdit->date(), m_timeEdit->time()); +} + +bool KexiDBDateTimeEdit::valueIsNull() +{ + return !m_dateEdit->date().isValid() || m_dateEdit->date().isNull() + || !m_timeEdit->time().isValid() || m_timeEdit->time().isNull(); +} + +bool KexiDBDateTimeEdit::valueIsEmpty() +{ + return m_cleared; +} + +bool KexiDBDateTimeEdit::isReadOnly() const +{ + //! @todo: data/time edit API has no readonly flag, + //! so use event filter to avoid changes made by keyboard or mouse when m_readOnly==true + return m_readOnly; //!isEnabled(); +} + +void KexiDBDateTimeEdit::setReadOnly(bool set) +{ + m_readOnly = set; +} + +QWidget* +KexiDBDateTimeEdit::widget() +{ + return m_dateEdit; +} + +bool KexiDBDateTimeEdit::cursorAtStart() +{ +#ifdef QDateTimeEditor_HACK + return m_dte_date && m_dateEdit->hasFocus() && m_dte_date->focusSection()==0; +#else + return false; +#endif +} + +bool KexiDBDateTimeEdit::cursorAtEnd() +{ +#ifdef QDateTimeEditor_HACK + return m_dte_time && m_timeEdit->hasFocus() + && m_dte_time->focusSection()==int(m_dte_time->sectionCount()-1); +#else + return false; +#endif +} + +void KexiDBDateTimeEdit::clear() +{ + m_dateEdit->setDate(QDate()); + m_timeEdit->setTime(QTime()); + m_cleared = true; +} + +void +KexiDBDateTimeEdit::slotValueChanged() +{ + m_cleared = false; +} + +void +KexiDBDateTimeEdit::slotShowDatePicker() +{ + QDate date = m_dateEdit->date(); + + m_datePicker->setDate(date); + m_datePicker->setFocus(); + m_datePicker->show(); + m_datePicker->setFocus(); +} + +void +KexiDBDateTimeEdit::acceptDate() +{ + m_dateEdit->setDate(m_datePicker->date()); + m_datePickerPopupMenu->hide(); +} + +bool +KexiDBDateTimeEdit::eventFilter(QObject *o, QEvent *e) +{ + if (o != m_datePicker) + return false; + + switch (e->type()) { + case QEvent::Hide: + m_datePickerPopupMenu->hide(); + break; + case QEvent::KeyPress: + case QEvent::KeyRelease: { + QKeyEvent *ke = (QKeyEvent *)e; + if (ke->key()==Qt::Key_Enter || ke->key()==Qt::Key_Return) { + //accepting picker + acceptDate(); + return true; + } + else if (ke->key()==Qt::Key_Escape) { + //canceling picker + m_datePickerPopupMenu->hide(); + return true; + } + else + m_datePickerPopupMenu->setFocus(); + break; + } + default: + break; + } + return false; +} + +QDateTime +KexiDBDateTimeEdit::dateTime() const +{ + return QDateTime(m_dateEdit->date(), m_timeEdit->time()); +} + +void +KexiDBDateTimeEdit::setDateTime(const QDateTime &dt) +{ + m_dateEdit->setDate(dt.date()); + m_timeEdit->setTime(dt.time()); +} + +#include "kexidbdatetimeedit.moc" diff --git a/kexi/plugins/forms/widgets/kexidbdatetimeedit.h b/kexi/plugins/forms/widgets/kexidbdatetimeedit.h new file mode 100644 index 00000000..1f185b16 --- /dev/null +++ b/kexi/plugins/forms/widgets/kexidbdatetimeedit.h @@ -0,0 +1,106 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr> + Copyright (C) 2004-2005 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 KexiDBDateTimeEdit_H +#define KexiDBDateTimeEdit_H + +#include "kexiformdataiteminterface.h" +#include <qdatetimeedit.h> + +class KDatePicker; +class QDateTimeEditor; +class KPopupMenu; + +//! @short A db-aware datetime editor +class KEXIFORMUTILS_EXPORT KexiDBDateTimeEdit : public QWidget, public KexiFormDataItemInterface +{ + Q_OBJECT + Q_PROPERTY(QString dataSource READ dataSource WRITE setDataSource DESIGNABLE true) + Q_PROPERTY(QCString dataSourceMimeType READ dataSourceMimeType WRITE setDataSourceMimeType DESIGNABLE true) + // properties copied from QDateTimeEdit + Q_PROPERTY( QDateTime dateTime READ dateTime WRITE setDateTime ) + Q_PROPERTY( bool readOnly READ isReadOnly WRITE setReadOnly DESIGNABLE true ) + + public: + enum Order { DMY, MDY, YMD, YDM }; + + KexiDBDateTimeEdit(const QDateTime &datetime, QWidget *parent, const char *name=0); + virtual ~KexiDBDateTimeEdit(); + + inline QString dataSource() const { return KexiFormDataItemInterface::dataSource(); } + inline QCString dataSourceMimeType() const { return KexiFormDataItemInterface::dataSourceMimeType(); } + virtual QVariant value(); + virtual void setInvalidState( const QString& displayText ); + + //! \return true if editor's value is null (not empty) + //! Used for checking if a given constraint within table of form is met. + virtual bool valueIsNull(); + + //! \return true if editor's value is empty (not necessary null). + //! Only few data types can accept "EMPTY" property + //! (use KexiDB::Field::hasEmptyProperty() to check this). + //! Used for checking if a given constraint within table or form is met. + virtual bool valueIsEmpty(); + + /*! \return 'readOnly' flag for this widget. */ + virtual bool isReadOnly() const; + + /*! \return the view widget of this item, e.g. line edit widget. */ + virtual QWidget* widget(); + + virtual bool cursorAtStart(); + virtual bool cursorAtEnd(); + virtual void clear(); + + virtual void setEnabled(bool enabled); + + // property functions + QDateTime dateTime() const; + + signals: + void dateTimeChanged(); + + public slots: + inline void setDataSource(const QString &ds) { KexiFormDataItemInterface::setDataSource(ds); } + inline void setDataSourceMimeType(const QCString &ds) { KexiFormDataItemInterface::setDataSourceMimeType(ds); } + void setDateTime(const QDateTime &dt); + virtual void setReadOnly(bool set); + + protected: + virtual void setValueInternal(const QVariant& add, bool removeOld); + virtual bool eventFilter(QObject *o, QEvent *e); + + protected slots: + void slotValueChanged(); + void slotShowDatePicker(); + void acceptDate(); + + private: + KDatePicker *m_datePicker; + QDateEdit* m_dateEdit; + QTimeEdit* m_timeEdit; + QDateTimeEditor *m_dte_date, *m_dte_time; + KPopupMenu *m_datePickerPopupMenu; + bool m_invalidState : 1; + bool m_cleared : 1; + bool m_readOnly : 1; +}; + +#endif diff --git a/kexi/plugins/forms/widgets/kexidbdoublespinbox.cpp b/kexi/plugins/forms/widgets/kexidbdoublespinbox.cpp new file mode 100644 index 00000000..67a2c1a6 --- /dev/null +++ b/kexi/plugins/forms/widgets/kexidbdoublespinbox.cpp @@ -0,0 +1,113 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr> + Copyright (C) 2004-2005 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 "kexidbdoublespinbox.h" + +#include <qlineedit.h> + +KexiDBDoubleSpinBox::KexiDBDoubleSpinBox(QWidget *parent, const char *name) + : KDoubleSpinBox(parent, name) , KexiFormDataItemInterface() +{ + connect(this, SIGNAL(valueChanged(double)), this, SLOT(slotValueChanged())); +} + +KexiDBDoubleSpinBox::~KexiDBDoubleSpinBox() +{ +} + +void KexiDBDoubleSpinBox::setInvalidState( const QString& displayText ) +{ + m_invalidState = true; + setEnabled(false); + setReadOnly(true); +//! @todo move this to KexiDataItemInterface::setInvalidStateInternal() ? + if (focusPolicy() & TabFocus) + setFocusPolicy(QWidget::ClickFocus); + setSpecialValueText(displayText); + KDoubleSpinBox::setValue(minValue()); +} + +void +KexiDBDoubleSpinBox::setEnabled(bool enabled) +{ + // prevent the user from reenabling the widget when it is in invalid state + if(enabled && m_invalidState) + return; + KDoubleSpinBox::setEnabled(enabled); +} + +void KexiDBDoubleSpinBox::setValueInternal(const QVariant&, bool ) +{ + KDoubleSpinBox::setValue(m_origValue.toDouble()); +} + +QVariant +KexiDBDoubleSpinBox::value() +{ + return KDoubleSpinBox::value(); +} + +void KexiDBDoubleSpinBox::slotValueChanged() +{ + signalValueChanged(); +} + +bool KexiDBDoubleSpinBox::valueIsNull() +{ + return cleanText().isEmpty(); +} + +bool KexiDBDoubleSpinBox::valueIsEmpty() +{ + return false; +} + +bool KexiDBDoubleSpinBox::isReadOnly() const +{ + return editor()->isReadOnly(); +} + +void KexiDBDoubleSpinBox::setReadOnly(bool set) +{ + editor()->setReadOnly(set); +} + +QWidget* +KexiDBDoubleSpinBox::widget() +{ + return this; +} + +bool KexiDBDoubleSpinBox::cursorAtStart() +{ + return false; //! \todo ? +} + +bool KexiDBDoubleSpinBox::cursorAtEnd() +{ + return false; //! \todo ? +} + +void KexiDBDoubleSpinBox::clear() +{ + KDoubleSpinBox::setValue(minValue()); +} + +#include "kexidbdoublespinbox.moc" diff --git a/kexi/plugins/forms/widgets/kexidbdoublespinbox.h b/kexi/plugins/forms/widgets/kexidbdoublespinbox.h new file mode 100644 index 00000000..c6bc627d --- /dev/null +++ b/kexi/plugins/forms/widgets/kexidbdoublespinbox.h @@ -0,0 +1,79 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr> + Copyright (C) 2004-2005 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 KexiDBDoubleSpinBox_H +#define KexiDBDoubleSpinBox_H + +#include "kexiformdataiteminterface.h" +#include <qwidget.h> +#include <knuminput.h> + +//! @short A db-aware int spin box +class KEXIFORMUTILS_EXPORT KexiDBDoubleSpinBox : public KDoubleSpinBox, public KexiFormDataItemInterface +{ + Q_OBJECT + Q_PROPERTY(QString dataSource READ dataSource WRITE setDataSource DESIGNABLE true) + Q_PROPERTY(QCString dataSourceMimeType READ dataSourceMimeType WRITE setDataSourceMimeType DESIGNABLE true) + Q_PROPERTY( bool readOnly READ isReadOnly WRITE setReadOnly DESIGNABLE true ) + + public: + KexiDBDoubleSpinBox(QWidget *parent, const char *name=0); + virtual ~KexiDBDoubleSpinBox(); + + inline QString dataSource() const { return KexiFormDataItemInterface::dataSource(); } + inline QCString dataSourceMimeType() const { return KexiFormDataItemInterface::dataSourceMimeType(); } + virtual QVariant value(); + virtual void setInvalidState( const QString& displayText ); + + //! \return true if editor's value is null (not empty) + //! Used for checking if a given constraint within table of form is met. + virtual bool valueIsNull(); + + //! \return true if editor's value is empty (not necessary null). + //! Only few data types can accept "EMPTY" property + //! (use KexiDB::Field::hasEmptyProperty() to check this). + //! Used for checking if a given constraint within table or form is met. + virtual bool valueIsEmpty(); + + /*! \return 'readOnly' flag for this widget. */ + virtual bool isReadOnly() const; + + /*! \return the view widget of this item, e.g. line edit widget. */ + virtual QWidget* widget(); + + virtual bool cursorAtStart(); + virtual bool cursorAtEnd(); + virtual void clear(); + + public slots: + virtual void setEnabled(bool enabled); + inline void setDataSource(const QString &ds) { KexiFormDataItemInterface::setDataSource(ds); } + inline void setDataSourceMimeType(const QCString &ds) { KexiFormDataItemInterface::setDataSourceMimeType(ds); } + void slotValueChanged(); + virtual void setReadOnly(bool set); + + protected: + virtual void setValueInternal(const QVariant& add, bool removeOld); + + private: + bool m_invalidState : 1; +}; + +#endif diff --git a/kexi/plugins/forms/widgets/kexidbform.cpp b/kexi/plugins/forms/widgets/kexidbform.cpp new file mode 100644 index 00000000..cff12c7c --- /dev/null +++ b/kexi/plugins/forms/widgets/kexidbform.cpp @@ -0,0 +1,714 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Lucijan Busch <lucijan@kde.org> + Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr> + Copyright (C) 2005-2007 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 <qobjectlist.h> +#include <qpainter.h> +#include <qcursor.h> +#include <qapplication.h> +#include <qfocusdata.h> + +#include <kdebug.h> + +#include "kexidbform.h" +#include "kexiformpart.h" +#include "kexiformscrollview.h" + +#include <formeditor/objecttree.h> +#include <formeditor/formmanager.h> +#include <formeditor/widgetlibrary.h> +#include <widget/tableview/kexidataawareobjectiface.h> +#include <widget/kexiscrollview.h> +#include <kexiutils/utils.h> + +//! @internal +class KexiDBForm::Private +{ + public: + Private() + : dataAwareObject(0) + , orderedFocusWidgetsIterator(orderedFocusWidgets) + , autoTabStops(false) + , popupFocused(false) + { + } + + ~Private() + { + } + + //! \return index of data-aware widget \a widget + int indexOfDataAwareWidget(QWidget *widget) const + { + if (!dynamic_cast<KexiDataItemInterface*>(widget)) + return -1; + return indexOfDataItem( dynamic_cast<KexiDataItemInterface*>(widget) ); + } + + //! \return index of data item \a item, or -1 if not found + int indexOfDataItem( KexiDataItemInterface* item ) const + { + QMapConstIterator<KexiDataItemInterface*, uint> indicesForDataAwareWidgetsIt( + indicesForDataAwareWidgets.find(item)); + if (indicesForDataAwareWidgetsIt == indicesForDataAwareWidgets.constEnd()) + return -1; + kexipluginsdbg << "KexiDBForm: column # for item: " + << indicesForDataAwareWidgetsIt.data() << endl; + return indicesForDataAwareWidgetsIt.data(); + } + + //! Sets orderedFocusWidgetsIterator member to a position pointing to \a widget + void setOrderedFocusWidgetsIteratorTo( QWidget *widget ) + { + if (orderedFocusWidgetsIterator.current() == widget) + return; + orderedFocusWidgetsIterator.toFirst(); + while (orderedFocusWidgetsIterator.current() && orderedFocusWidgetsIterator.current()!=widget) + ++orderedFocusWidgetsIterator; + } + + KexiDataAwareObjectInterface* dataAwareObject; + //! ordered list of focusable widgets (can be both data-widgets or buttons, etc.) + QPtrList<QWidget> orderedFocusWidgets; + //! ordered list of data-aware widgets + QPtrList<QWidget> orderedDataAwareWidgets; + QMap<KexiDataItemInterface*, uint> indicesForDataAwareWidgets; //!< a subset of orderedFocusWidgets mapped to indices + QPtrListIterator<QWidget> orderedFocusWidgetsIterator; + QPixmap buffer; //!< stores grabbed entire form's area for redraw + QRect prev_rect; //!< previously selected rectangle +// QGuardedPtr<QWidget> widgetFocusedBeforePopup; + bool autoTabStops : 1; + bool popupFocused : 1; //!< used in KexiDBForm::eventFilter() +}; + +//======================== + +KexiDBForm::KexiDBForm(QWidget *parent, KexiDataAwareObjectInterface* dataAwareObject, + const char *name/*, KexiDB::Connection *conn*/) + : KexiDBFormBase(parent, name) + , KexiFormDataItemInterface() + , d(new Private()) +{ + installEventFilter(this); +//test setDisplayMode( KexiGradientWidget::SimpleGradient ); + editedItem = 0; + d->dataAwareObject = dataAwareObject; + m_hasFocusableWidget = false; + + kexipluginsdbg << "KexiDBForm::KexiDBForm(): " << endl; + setCursor(QCursor(Qt::ArrowCursor)); //to avoid keeping Size cursor when moving from form's boundaries + setAcceptDrops( true ); +} + +KexiDBForm::~KexiDBForm() +{ + kexipluginsdbg << "KexiDBForm::~KexiDBForm(): close" << endl; + delete d; +} + +KexiDataAwareObjectInterface* KexiDBForm::dataAwareObject() const { return d->dataAwareObject; } + +//repaint all children widgets +static void repaintAll(QWidget *w) +{ + QObjectList *list = w->queryList("QWidget"); + QObjectListIt it(*list); + for (QObject *obj; (obj=it.current()); ++it ) { + static_cast<QWidget*>(obj)->repaint(); + } + delete list; +} + +void +KexiDBForm::drawRect(const QRect& r, int type) +{ + QValueList<QRect> l; + l.append(r); + drawRects(l, type); +} + +void +KexiDBForm::drawRects(const QValueList<QRect> &list, int type) +{ + QPainter p; + p.begin(this, true); + bool unclipped = testWFlags( WPaintUnclipped ); + setWFlags( WPaintUnclipped ); + + if (d->prev_rect.isValid()) { + //redraw prev. selection's rectangle + p.drawPixmap( QPoint(d->prev_rect.x()-2, d->prev_rect.y()-2), d->buffer, + QRect(d->prev_rect.x()-2, d->prev_rect.y()-2, d->prev_rect.width()+4, d->prev_rect.height()+4)); + } + p.setBrush(QBrush::NoBrush); + if(type == 1) // selection rect + p.setPen(QPen(white, 1, Qt::DotLine)); + else if(type == 2) // insert rect + p.setPen(QPen(white, 2)); + p.setRasterOp(XorROP); + + d->prev_rect = QRect(); + QValueList<QRect>::ConstIterator endIt = list.constEnd(); + for(QValueList<QRect>::ConstIterator it = list.constBegin(); it != endIt; ++it) { + p.drawRect(*it); + if (d->prev_rect.isValid()) + d->prev_rect = d->prev_rect.unite(*it); + else + d->prev_rect = *it; + } + + if (!unclipped) + clearWFlags( WPaintUnclipped ); + p.end(); +} + +void +KexiDBForm::initBuffer() +{ + repaintAll(this); + d->buffer.resize( width(), height() ); + d->buffer = QPixmap::grabWindow( winId() ); + d->prev_rect = QRect(); +} + +void +KexiDBForm::clearForm() +{ + QPainter p; + p.begin(this, true); + bool unclipped = testWFlags( WPaintUnclipped ); + setWFlags( WPaintUnclipped ); + + //redraw entire form surface + p.drawPixmap( QPoint(0,0), d->buffer, QRect(0,0,d->buffer.width(), d->buffer.height()) ); + + if (!unclipped) + clearWFlags( WPaintUnclipped ); + p.end(); + + repaintAll(this); +} + +void +KexiDBForm::highlightWidgets(QWidget *from, QWidget *to)//, const QPoint &point) +{ + QPoint fromPoint, toPoint; + if(from && from->parentWidget() && (from != this)) + fromPoint = from->parentWidget()->mapTo(this, from->pos()); + if(to && to->parentWidget() && (to != this)) + toPoint = to->parentWidget()->mapTo(this, to->pos()); + + QPainter p; + p.begin(this, true); + bool unclipped = testWFlags( WPaintUnclipped ); + setWFlags( WPaintUnclipped ); + + if (d->prev_rect.isValid()) { + //redraw prev. selection's rectangle + p.drawPixmap( QPoint(d->prev_rect.x(), d->prev_rect.y()), d->buffer, + QRect(d->prev_rect.x(), d->prev_rect.y(), d->prev_rect.width(), d->prev_rect.height())); + } + + p.setPen( QPen(Qt::red, 2) ); + + if(to) + { + QPixmap pix1 = QPixmap::grabWidget(from); + QPixmap pix2 = QPixmap::grabWidget(to); + + if((from != this) && (to != this)) + p.drawLine( from->parentWidget()->mapTo(this, from->geometry().center()), to->parentWidget()->mapTo(this, to->geometry().center()) ); + + p.drawPixmap(fromPoint.x(), fromPoint.y(), pix1); + p.drawPixmap(toPoint.x(), toPoint.y(), pix2); + + if(to == this) + p.drawRoundRect(2, 2, width()-4, height()-4, 4, 4); + else + p.drawRoundRect(toPoint.x(), toPoint.y(), to->width(), to->height(), 5, 5); + } + + if(from == this) + p.drawRoundRect(2, 2, width()-4, height()-4, 4, 4); + else + p.drawRoundRect(fromPoint.x(), fromPoint.y(), from->width(), from->height(), 5, 5); + + if((to == this) || (from == this)) + d->prev_rect = QRect(0, 0, d->buffer.width(), d->buffer.height()); + else if(to) + { + d->prev_rect.setX( (fromPoint.x() < toPoint.x()) ? (fromPoint.x() - 5) : (toPoint.x() - 5) ); + d->prev_rect.setY( (fromPoint.y() < toPoint.y()) ? (fromPoint.y() - 5) : (toPoint.y() - 5) ); + d->prev_rect.setRight( (fromPoint.x() < toPoint.x()) ? (toPoint.x() + to->width() + 10) : (fromPoint.x() + from->width() + 10) ); + d->prev_rect.setBottom( (fromPoint.y() < toPoint.y()) ? (toPoint.y() + to->height() + 10) : (fromPoint.y() + from->height() + 10) ) ; + } + else + d->prev_rect = QRect(fromPoint.x()- 5, fromPoint.y() -5, from->width() + 10, from->height() + 10); + + if (!unclipped) + clearWFlags( WPaintUnclipped ); + p.end(); +} + +QSize +KexiDBForm::sizeHint() const +{ + //todo: find better size (user configured?) + return QSize(400,300); +} + +void KexiDBForm::setInvalidState( const QString& displayText ) +{ + Q_UNUSED( displayText ); + + //! @todo draw "invalid data source" text on the surface? +} + +bool KexiDBForm::autoTabStops() const +{ + return d->autoTabStops; +} + +void KexiDBForm::setAutoTabStops(bool set) +{ + d->autoTabStops = set; +} + +QPtrList<QWidget>* KexiDBForm::orderedFocusWidgets() const +{ + return &d->orderedFocusWidgets; +} + +QPtrList<QWidget>* KexiDBForm::orderedDataAwareWidgets() const +{ + return &d->orderedDataAwareWidgets; +} + +void KexiDBForm::updateTabStopsOrder(KFormDesigner::Form* form) +{ + QWidget *fromWidget = 0; + //QWidget *topLevelWidget = form->widget()->topLevelWidget(); +//js form->updateTabStopsOrder(); //certain widgets can have now updated focusPolicy properties, fix this + uint numberOfDataAwareWidgets = 0; +// if (d->orderedFocusWidgets.isEmpty()) { + //generate a new list + for (KFormDesigner::ObjectTreeListIterator it(form->tabStopsIterator()); it.current(); ++it) { + if (it.current()->widget()->focusPolicy() & QWidget::TabFocus) { + //this widget has tab focus: + it.current()->widget()->installEventFilter(this); + //also filter events for data-aware children of this widget (i.e. KexiDBAutoField's editors) + QObjectList *children = it.current()->widget()->queryList("QWidget"); + for (QObjectListIt childrenIt(*children); childrenIt.current(); ++childrenIt) { + // if (dynamic_cast<KexiFormDataItemInterface*>(childrenIt.current())) { + kexipluginsdbg << "KexiDBForm::updateTabStopsOrder(): also adding '" + << childrenIt.current()->className() << " " << childrenIt.current()->name() + << "' child to filtered widgets" << endl; + //it.current()->widget()->installEventFilter(static_cast<QWidget*>(childrenIt.current())); + childrenIt.current()->installEventFilter(this); + // } + } + delete children; + if (fromWidget) { + kexipluginsdbg << "KexiDBForm::updateTabStopsOrder() tab order: " << fromWidget->name() + << " -> " << it.current()->widget()->name() << endl; + // setTabOrder( fromWidget, it.current()->widget() ); + } + fromWidget = it.current()->widget(); + d->orderedFocusWidgets.append( it.current()->widget() ); + } + + KexiFormDataItemInterface* dataItem = dynamic_cast<KexiFormDataItemInterface*>( it.current()->widget() ); + if (dataItem && !dataItem->dataSource().isEmpty()) { + kexipluginsdbg << "#" << numberOfDataAwareWidgets << ": " + << dataItem->dataSource() << " (" << it.current()->widget()->name() << ")" << endl; + +// /*! @todo d->indicesForDataAwareWidgets SHOULDNT BE UPDATED HERE BECAUSE +// THERE CAN BE ALSO NON-TABSTOP DATA WIDGETS! +// */ + d->indicesForDataAwareWidgets.replace( + dataItem, + numberOfDataAwareWidgets ); + numberOfDataAwareWidgets++; + + d->orderedDataAwareWidgets.append( it.current()->widget() ); + } + }//for +// } +/* else { + //restore ordering + for (QPtrListIterator<QWidget> it(d->orderedFocusWidgets); it.current(); ++it) { + if (fromWidget) { + kdDebug() << "KexiDBForm::updateTabStopsOrder() tab order: " << fromWidget->name() + << " -> " << it.current()->name() << endl; + setTabOrder( fromWidget, it.current() ); + } + fromWidget = it.current(); + } +// SET_FOCUS_USING_REASON(focusWidget(), QFocusEvent::Tab); + }*/ +} + +void KexiDBForm::updateTabStopsOrder() +{ + for (QPtrListIterator<QWidget> it( d->orderedFocusWidgets ); it.current();) { + if (! (it.current()->focusPolicy() & QWidget::TabFocus)) + d->orderedFocusWidgets.remove( it.current() ); + else + ++it; + } +} + +void KexiDBForm::updateReadOnlyFlags() +{ + for (QPtrListIterator<QWidget> it(d->orderedDataAwareWidgets); it.current(); ++it) { + KexiFormDataItemInterface* dataItem = dynamic_cast<KexiFormDataItemInterface*>( it.current() ); + if (dataItem && !dataItem->dataSource().isEmpty()) { + if (dataAwareObject()->isReadOnly()) { + dataItem->setReadOnly( true ); + } + } + } +} + +bool KexiDBForm::eventFilter( QObject * watched, QEvent * e ) +{ + //kexipluginsdbg << e->type() << endl; + if (e->type()==QEvent::Resize && watched == this) + kexipluginsdbg << "RESIZE" << endl; + if (e->type()==QEvent::KeyPress) { + if (preview()) { + QKeyEvent *ke = static_cast<QKeyEvent*>(e); + const int key = ke->key(); + bool tab = ke->state() == Qt::NoButton && key == Qt::Key_Tab; + bool backtab = ((ke->state() == Qt::NoButton || ke->state() == Qt::ShiftButton) && key == Qt::Key_Backtab) + || (ke->state() == Qt::ShiftButton && key == Qt::Key_Tab); + QObject *o = watched; //focusWidget(); + QWidget* realWidget = dynamic_cast<QWidget*>(o); //will beused below (for tab/backtab handling) + + if (!tab && !backtab) { + //for buttons, left/up and right/down keys act like tab/backtab (see qbutton.cpp) + if (realWidget->inherits("QButton")) { + if (ke->state() == Qt::NoButton && (key == Qt::Key_Right || key == Qt::Key_Down)) + tab = true; + else if (ke->state() == Qt::NoButton && (key == Qt::Key_Left || key == Qt::Key_Up)) + backtab = true; + } + } + + if (!tab && !backtab) { + // allow the editor widget to grab the key press event + while (true) { + if (!o || o == dynamic_cast<QObject*>(d->dataAwareObject)) + break; + if (dynamic_cast<KexiFormDataItemInterface*>(o)) { + realWidget = dynamic_cast<QWidget*>(o); //will be used below + if (realWidget == this) //we have encountered 'this' form surface, give up + return false; + KexiFormDataItemInterface* dataItemIface = dynamic_cast<KexiFormDataItemInterface*>(o); + while (dataItemIface) { + if (dataItemIface->keyPressed(ke)) + return false; + dataItemIface = dynamic_cast<KexiFormDataItemInterface*>(dataItemIface->parentInterface()); //try in parent, e.g. in combobox + } + break; + } + o = o->parent(); + } + // try to handle global shortcuts at the KexiDataAwareObjectInterface + // level (e.g. for "next record" action) + int curRow = d->dataAwareObject->currentRow(); + int curCol = d->dataAwareObject->currentColumn(); + bool moveToFirstField; //if true, we'll move focus to the first field (in tab order) + bool moveToLastField; //if true, we'll move focus to the first field (in tab order) + if (! (ke->state() == Qt::NoButton && (key == Qt::Key_Home + || key == Qt::Key_End || key == Qt::Key_Down || key == Qt::Key_Up)) + /* ^^ home/end/down/up are already handled by widgets */ + && d->dataAwareObject->handleKeyPress( + ke, curRow, curCol, false/*!fullRowSelection*/, &moveToFirstField, &moveToLastField)) + { + if (ke->isAccepted()) + return true; + QWidget* widgetToFocus; + if (moveToFirstField) { + widgetToFocus = d->orderedFocusWidgets.first(); //? + curCol = d->indexOfDataAwareWidget( widgetToFocus ); + } + else if (moveToLastField) { + widgetToFocus = d->orderedFocusWidgets.last(); //? + curCol = d->indexOfDataAwareWidget( widgetToFocus ); + } + else + widgetToFocus = d->orderedDataAwareWidgets.at( curCol ); //? + + d->dataAwareObject->setCursorPosition( curRow, curCol ); + + if (widgetToFocus) + widgetToFocus->setFocus(); + else + kexipluginswarn << "KexiDBForm::eventFilter(): widgetToFocus not found!" << endl; + + ke->accept(); + return true; + } + if (key == Qt::Key_Delete && ke->state()==Qt::ControlButton) { +//! @todo remove hardcoded shortcuts: can be reconfigured... + d->dataAwareObject->deleteCurrentRow(); + return true; + } + } + // handle Esc key + if (ke->state() == Qt::NoButton && key == Qt::Key_Escape) { + //cancel field editing/row editing if possible + if (d->dataAwareObject->cancelEditor()) + return true; + else if (d->dataAwareObject->cancelRowEdit()) + return true; + return false; // canceling not needed - pass the event to the active widget + } + // jstaniek: Fix for Qt bug (handling e.g. Alt+2, Ctrl+2 keys on every platform) + // It's important because we're using alt+2 short cut by default + // Damn! I've reported this to Trolltech in November 2004 - still not fixed. + if (ke->isAccepted() && (ke->state() & Qt::AltButton) && ke->text()>="0" && ke->text()<="9") + return true; + + if (tab || backtab) { + //the watched widget can be a subwidget of a real widget, e.g. a drop down button of image box: find it + while (!KexiFormPart::library()->widgetInfoForClassName(realWidget->className())) + realWidget = realWidget->parentWidget(); + if (!realWidget) + return true; //ignore + //the watched widget can be a subwidget of a real widget, e.g. autofield: find it + //QWidget* realWidget = static_cast<QWidget*>(watched); + while (dynamic_cast<KexiDataItemInterface*>(realWidget) && dynamic_cast<KexiDataItemInterface*>(realWidget)->parentInterface()) + realWidget = dynamic_cast<QWidget*>( dynamic_cast<KexiDataItemInterface*>(realWidget)->parentInterface() ); + + d->setOrderedFocusWidgetsIteratorTo( realWidget ); + kexipluginsdbg << realWidget->name() << endl; + + // find next/prev widget to focus + QWidget *widgetToUnfocus = realWidget; + QWidget *widgetToFocus = 0; + bool wasAtFirstWidget = false; //used to protect against infinite loop + while (true) { + if (tab) { + if (d->orderedFocusWidgets.first() && realWidget == d->orderedFocusWidgets.last()) { + if (wasAtFirstWidget) + break; + d->orderedFocusWidgetsIterator.toFirst(); + wasAtFirstWidget = true; + } + else if (realWidget == d->orderedFocusWidgetsIterator.current()) { + ++d->orderedFocusWidgetsIterator; //next + } + else + return true; //ignore + } + else {//backtab + if (d->orderedFocusWidgets.last() && realWidget == d->orderedFocusWidgets.first()) { + d->orderedFocusWidgetsIterator.toLast(); + } + else if (realWidget == d->orderedFocusWidgetsIterator.current()) { + --d->orderedFocusWidgetsIterator; //prev + } + else + return true; //ignore + } + + widgetToFocus = d->orderedFocusWidgetsIterator.current(); + + QObject *pageFor_widgetToFocus = 0; + KFormDesigner::TabWidget *tabWidgetFor_widgetToFocus + = KFormDesigner::findParent<KFormDesigner::TabWidget>( + widgetToFocus, "KFormDesigner::TabWidget", pageFor_widgetToFocus); + if (tabWidgetFor_widgetToFocus && tabWidgetFor_widgetToFocus->currentPage()!=pageFor_widgetToFocus) { + realWidget = widgetToFocus; + continue; //the new widget to focus is placed on invisible tab page: move to next widget + } + break; + }//while + + //set focus, but don't use just setFocus() because certain widgets + //behaves differently (e.g. QLineEdit calls selectAll()) when + //focus event's reason is QFocusEvent::Tab + if (widgetToFocus->focusProxy()) + widgetToFocus = widgetToFocus->focusProxy(); + if (widgetToFocus && d->dataAwareObject->acceptEditor()) { + if (tab) { + //try to accept this will validate the current input (if any) + KexiUtils::unsetFocusWithReason(widgetToUnfocus, QFocusEvent::Tab); + KexiUtils::setFocusWithReason(widgetToFocus, QFocusEvent::Tab); + kexipluginsdbg << "focusing " << widgetToFocus->name() << endl; + } + else {//backtab + KexiUtils::unsetFocusWithReason(widgetToUnfocus, QFocusEvent::Backtab); + //set focus, see above note + KexiUtils::setFocusWithReason(d->orderedFocusWidgetsIterator.current(), QFocusEvent::Backtab); + kexipluginsdbg << "focusing " << d->orderedFocusWidgetsIterator.current()->name() << endl; + } + } + return true; + } + } + } + else if (e->type()==QEvent::FocusIn) { + bool focusDataWidget = preview(); + if (static_cast<QFocusEvent*>(e)->reason()==QFocusEvent::Popup) { + kdDebug() << "->>> focus IN, popup" <<endl; + focusDataWidget = !d->popupFocused; + d->popupFocused = false; +// if (d->widgetFocusedBeforePopup) { +// watched = d->widgetFocusedBeforePopup; +// d->widgetFocusedBeforePopup = 0; +// } + } + + if (focusDataWidget) { + kexipluginsdbg << "KexiDBForm: FocusIn: " << watched->className() << " " << watched->name() << endl; + if (d->dataAwareObject) { + QWidget *dataItem = dynamic_cast<QWidget*>(watched); + while (dataItem) { + while (dataItem && !dynamic_cast<KexiDataItemInterface*>(dataItem)) + dataItem = dataItem->parentWidget(); + if (!dataItem) + break; + kexipluginsdbg << "KexiDBForm: FocusIn: FOUND " << dataItem->className() << " " << dataItem->name() << endl; + + const int index = d->indexOfDataAwareWidget(dataItem); + if (index>=0) { + kexipluginsdbg << "KexiDBForm: moving cursor to column #" << index << endl; + editedItem = 0; + if ((int)index!=d->dataAwareObject->currentColumn()) { + d->dataAwareObject->setCursorPosition( d->dataAwareObject->currentRow(), index /*column*/ ); + } + break; + } + else + dataItem = dataItem->parentWidget(); + + dataItem->update(); + } + } + } + } + else if (e->type()==QEvent::FocusOut) { + if (static_cast<QFocusEvent*>(e)->reason()==QFocusEvent::Popup) { + //d->widgetFocusedBeforePopup = (QWidget*)watched; + d->popupFocused = true; + } + else + d->popupFocused = false; +// d->widgetFocusedBeforePopup = 0; +// kdDebug() << "e->type()==QEvent::FocusOut " << watched->className() << " " <<watched->name() << endl; +// UNSET_FOCUS_USING_REASON(watched, static_cast<QFocusEvent*>(e)->reason()); + } + return KexiDBFormBase::eventFilter(watched, e); +} + +bool KexiDBForm::valueIsNull() +{ + return true; +} + +bool KexiDBForm::valueIsEmpty() +{ + return true; +} + +bool KexiDBForm::isReadOnly() const +{ + if (d->dataAwareObject) + return d->dataAwareObject->isReadOnly(); +//! @todo ? + return false; +} + +void KexiDBForm::setReadOnly( bool readOnly ) +{ + if (d->dataAwareObject) + d->dataAwareObject->setReadOnly( readOnly ); //??? +} + +QWidget* KexiDBForm::widget() +{ + return this; +} + +bool KexiDBForm::cursorAtStart() +{ + return false; +} + +bool KexiDBForm::cursorAtEnd() +{ + return false; +} + +void KexiDBForm::clear() +{ + //! @todo clear all fields? +} + +bool KexiDBForm::preview() const { + return dynamic_cast<KexiScrollView*>(d->dataAwareObject) + ? dynamic_cast<KexiScrollView*>(d->dataAwareObject)->preview() : false; +} + +void KexiDBForm::dragMoveEvent( QDragMoveEvent *e ) +{ + KexiDBFormBase::dragMoveEvent( e ); + emit handleDragMoveEvent(e); +} + +void KexiDBForm::dropEvent( QDropEvent *e ) +{ + KexiDBFormBase::dropEvent( e ); + emit handleDropEvent(e); +} + +void KexiDBForm::setCursor( const QCursor & cursor ) +{ + //js: empty, to avoid fscking problems with random cursors! + //! @todo? + + if (KFormDesigner::FormManager::self()->isInserting()) //exception + KexiDBFormBase::setCursor(cursor); +} + +//! @todo: Qt4? XORed resize rectangles instead of black widgets +/* +void KexiDBForm::paintEvent( QPaintEvent *e ) +{ + QPainter p; + p.begin(this, true); + bool unclipped = testWFlags( WPaintUnclipped ); + setWFlags( WPaintUnclipped ); + + p.setPen(white); + p.setRasterOp(XorROP); + p.drawLine(e->rect().topLeft(), e->rect().bottomRight()); + + if (!unclipped) + clearWFlags( WPaintUnclipped ); + p.end(); + KexiDBFormBase::paintEvent(e); +} +*/ + +#include "kexidbform.moc" diff --git a/kexi/plugins/forms/widgets/kexidbform.h b/kexi/plugins/forms/widgets/kexidbform.h new file mode 100644 index 00000000..81a71bba --- /dev/null +++ b/kexi/plugins/forms/widgets/kexidbform.h @@ -0,0 +1,139 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Lucijan Busch <lucijan@kde.org> + Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr> + Copyright (C) 2005-2007 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 KEXIDBFORM_H +#define KEXIDBFORM_H + +#include <qpixmap.h> + +#include <formeditor/form.h> +#include "../kexiformdataiteminterface.h" + +#ifdef KEXI_USE_GRADIENT_WIDGET +#include <kexigradientwidget.h> +# define KexiDBFormBase KexiGradientWidget +#else +# define KexiDBFormBase QWidget +#endif + +class KexiDataAwareObjectInterface; +class KexiFormScrollView; + +//! @short A DB-aware form widget, acting as form's toplevel widget +class KEXIFORMUTILS_EXPORT KexiDBForm : + public KexiDBFormBase, + public KFormDesigner::FormWidget, + public KexiFormDataItemInterface +{ + Q_OBJECT + Q_PROPERTY(QString dataSource READ dataSource WRITE setDataSource DESIGNABLE true) + Q_PROPERTY(QCString dataSourceMimeType READ dataSourceMimeType WRITE setDataSourceMimeType DESIGNABLE true) + Q_PROPERTY(bool autoTabStops READ autoTabStops WRITE setAutoTabStops DESIGNABLE true) + //original "size" property is not designable, so here's a custom (not storable) replacement + Q_PROPERTY( QSize sizeInternal READ sizeInternal WRITE resizeInternal DESIGNABLE true STORED false ) + public: + KexiDBForm(QWidget *parent, KexiDataAwareObjectInterface* dataAwareObject, const char *name="kexi_dbform"); + virtual ~KexiDBForm(); + + KexiDataAwareObjectInterface* dataAwareObject() const; + + inline QString dataSource() const { return KexiFormDataItemInterface::dataSource(); } + inline QCString dataSourceMimeType() const { return KexiFormDataItemInterface::dataSourceMimeType(); } + + //! no effect + QVariant value() { return QVariant(); } + + virtual void setInvalidState( const QString& displayText ); + + virtual void drawRect(const QRect& r, int type); + virtual void drawRects(const QValueList<QRect> &list, int type); + virtual void initBuffer(); + virtual void clearForm(); + virtual void highlightWidgets(QWidget *from, QWidget *to/*, const QPoint &p*/); + + virtual QSize sizeHint() const; + + bool autoTabStops() const; + + QPtrList<QWidget>* orderedFocusWidgets() const; + + QPtrList<QWidget>* orderedDataAwareWidgets() const; + + void updateTabStopsOrder(KFormDesigner::Form* form); + + void updateTabStopsOrder(); + + virtual bool eventFilter ( QObject * watched, QEvent * e ); + + virtual bool valueIsNull(); + virtual bool valueIsEmpty(); + virtual bool isReadOnly() const; + virtual QWidget* widget(); + virtual bool cursorAtStart(); + virtual bool cursorAtEnd(); + virtual void clear(); + + bool preview() const; + + virtual void setCursor( const QCursor & cursor ); + + public slots: + void setAutoTabStops(bool set); + inline void setDataSource(const QString &ds) { KexiFormDataItemInterface::setDataSource(ds); } + inline void setDataSourceMimeType(const QCString &ds) { KexiFormDataItemInterface::setDataSourceMimeType(ds); } + + //! This implementation just disables read only widget + virtual void setReadOnly( bool readOnly ); + + //! @internal for sizeInternal property + QSize sizeInternal() const { return KexiDBFormBase::size(); } + + //! @internal for sizeInternal property + void resizeInternal(const QSize& s) { KexiDBFormBase::resize(s); } + + signals: + void handleDragMoveEvent(QDragMoveEvent *e); + void handleDropEvent(QDropEvent *e); + + protected: + //! no effect + virtual void setValueInternal(const QVariant&, bool) {} + + //! Used to emit handleDragMoveEvent() signal needed to control dragging over the container's surface + virtual void dragMoveEvent( QDragMoveEvent *e ); + + //! Used to emit handleDropEvent() signal needed to control dropping on the container's surface + virtual void dropEvent( QDropEvent *e ); + + //! called from KexiFormScrollView::initDataContents() + void updateReadOnlyFlags(); +// virtual void paintEvent( QPaintEvent * ); + + //! Points to a currently edited data item. + //! It is cleared when the focus is moved to other + KexiFormDataItemInterface *editedItem; + + class Private; + Private *d; + + friend class KexiFormScrollView; +}; + +#endif diff --git a/kexi/plugins/forms/widgets/kexidbimagebox.cpp b/kexi/plugins/forms/widgets/kexidbimagebox.cpp new file mode 100644 index 00000000..82e70086 --- /dev/null +++ b/kexi/plugins/forms/widgets/kexidbimagebox.cpp @@ -0,0 +1,870 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr> + Copyright (C) 2004-2007 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 "kexidbimagebox.h" + +#include <qapplication.h> +#include <qpixmap.h> +#include <qstyle.h> +#include <qclipboard.h> +#include <qtooltip.h> +#include <qimage.h> +#include <qbuffer.h> +#include <qfiledialog.h> +#include <qpainter.h> + +#include <kdebug.h> +#include <kpopupmenu.h> +#include <klocale.h> +#include <kiconloader.h> +#include <kfiledialog.h> +#include <kimageio.h> +#include <kstandarddirs.h> +#include <kstaticdeleter.h> +#include <kimageeffect.h> +#include <kstdaccel.h> +#include <kmessagebox.h> +#include <kguiitem.h> + +#include <widget/utils/kexidropdownbutton.h> +#include <widget/utils/kexicontextmenuutils.h> +#include <kexiutils/utils.h> +#include <kexidb/field.h> +#include <kexidb/utils.h> +#include <kexidb/queryschema.h> +#include <formeditor/widgetlibrary.h> + +#ifdef Q_WS_WIN +#include <win32_utils.h> +#include <krecentdirs.h> +#endif + +#include "kexidbutils.h" +#include "../kexiformpart.h" + +static KStaticDeleter<QPixmap> KexiDBImageBox_pmDeleter; +static QPixmap* KexiDBImageBox_pm = 0; +static KStaticDeleter<QPixmap> KexiDBImageBox_pmSmallDeleter; +static QPixmap* KexiDBImageBox_pmSmall = 0; + +KexiDBImageBox::KexiDBImageBox( bool designMode, QWidget *parent, const char *name ) + : KexiFrame( parent, name, Qt::WNoAutoErase ) + , KexiFormDataItemInterface() + , m_alignment(Qt::AlignAuto|Qt::AlignTop) + , m_designMode(designMode) + , m_readOnly(false) + , m_scaledContents(false) + , m_keepAspectRatio(true) + , m_insideSetData(false) + , m_setFocusOnButtonAfterClosingPopup(false) + , m_lineWidthChanged(false) + , m_paintEventEnabled(true) + , m_dropDownButtonVisible(true) + , m_insideSetPalette(false) +{ + installEventFilter(this); + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + + //setup popup menu + m_popupMenu = new KexiImageContextMenu(this); + m_popupMenu->installEventFilter(this); + + if (m_designMode) { + m_chooser = 0; + } + else { + m_chooser = new KexiDropDownButton(this); + m_chooser->setFocusPolicy(StrongFocus); + m_chooser->setPopup(m_popupMenu); + setFocusProxy(m_chooser); + m_chooser->installEventFilter(this); +// m_chooser->setPalette(qApp->palette()); +// hlyr->addWidget(m_chooser); + } + + setBackgroundMode(Qt::NoBackground); + setFrameShape(QFrame::Box); + setFrameShadow(QFrame::Plain); + setFrameColor(Qt::black); + + m_paletteBackgroundColorChanged = false; //set this here, not before + + connect(m_popupMenu, SIGNAL(updateActionsAvailabilityRequested(bool&, bool&)), + this, SLOT(slotUpdateActionsAvailabilityRequested(bool&, bool&))); + connect(m_popupMenu, SIGNAL(insertFromFileRequested(const KURL&)), + this, SLOT(handleInsertFromFileAction(const KURL&))); + connect(m_popupMenu, SIGNAL(saveAsRequested(const QString&)), + this, SLOT(handleSaveAsAction(const QString&))); + connect(m_popupMenu, SIGNAL(cutRequested()), + this, SLOT(handleCutAction())); + connect(m_popupMenu, SIGNAL(copyRequested()), + this, SLOT(handleCopyAction())); + connect(m_popupMenu, SIGNAL(pasteRequested()), + this, SLOT(handlePasteAction())); + connect(m_popupMenu, SIGNAL(clearRequested()), + this, SLOT(clear())); + connect(m_popupMenu, SIGNAL(showPropertiesRequested()), + this, SLOT(handleShowPropertiesAction())); + +// connect(m_popupMenu, SIGNAL(aboutToHide()), this, SLOT(slotAboutToHidePopupMenu())); +// if (m_chooser) { + //we couldn't use m_chooser->setPopup() because of drawing problems +// connect(m_chooser, SIGNAL(pressed()), this, SLOT(slotChooserPressed())); +// connect(m_chooser, SIGNAL(released()), this, SLOT(slotChooserReleased())); +// connect(m_chooser, SIGNAL(toggled(bool)), this, SLOT(slotToggled(bool))); +// } + + setDataSource( QString::null ); //to initialize popup menu and actions availability +} + +KexiDBImageBox::~KexiDBImageBox() +{ +} + +KexiImageContextMenu* KexiDBImageBox::contextMenu() const +{ + return m_popupMenu; +} + +QVariant KexiDBImageBox::value() +{ + if (dataSource().isEmpty()) { + //not db-aware + return QVariant(); + } + //db-aware mode + return m_value; //todo + //return QVariant(); //todo +} + +void KexiDBImageBox::setValueInternal( const QVariant& add, bool removeOld, bool loadPixmap ) +{ + if (isReadOnly()) + return; + m_popupMenu->hide(); + if (removeOld) + m_value = add.toByteArray(); + else //do not add "m_origValue" to "add" as this is QByteArray + m_value = m_origValue.toByteArray(); + bool ok = !m_value.isEmpty(); + if (ok) { + ///unused (m_valueMimeType is not available unless the px is inserted) QString type( KImageIO::typeForMime(m_valueMimeType) ); + ///ok = KImageIO::canRead( type ); + ok = loadPixmap ? m_pixmap.loadFromData(m_value) : true; //, type.latin1()); + if (!ok) { + //! @todo inform about error? + } + } + if (!ok) { + m_valueMimeType = QString::null; + m_pixmap = QPixmap(); + } + repaint(); +} + +void KexiDBImageBox::setInvalidState( const QString& displayText ) +{ + Q_UNUSED( displayText ); + +// m_pixmapLabel->setPixmap(QPixmap()); + if (!dataSource().isEmpty()) { + m_value = QByteArray(); + } +// m_pixmap = QPixmap(); +// m_originalFileName = QString::null; + +//! @todo m_pixmapLabel->setText( displayText ); + + if (m_chooser) + m_chooser->hide(); + setReadOnly(true); +} + +bool KexiDBImageBox::valueIsNull() +{ + return m_value.isEmpty(); +// return !m_pixmapLabel->pixmap() || m_pixmapLabel->pixmap()->isNull(); +} + +bool KexiDBImageBox::valueIsEmpty() +{ + return false; +} + +bool KexiDBImageBox::isReadOnly() const +{ + return m_readOnly; +} + +void KexiDBImageBox::setReadOnly(bool set) +{ + m_readOnly = set; +} + +QPixmap KexiDBImageBox::pixmap() const +{ + if (dataSource().isEmpty()) { + //not db-aware + return m_data.pixmap(); + } + //db-aware mode + return m_pixmap; +} + +uint KexiDBImageBox::pixmapId() const +{ + if (dataSource().isEmpty()) {// && !m_data.stored()) { + //not db-aware + return m_data.id(); + } + return 0; +} + +void KexiDBImageBox::setPixmapId(uint id) +{ + if (m_insideSetData) //avoid recursion + return; + setData(KexiBLOBBuffer::self()->objectForId( id, /*unstored*/false )); + repaint(); +} + +uint KexiDBImageBox::storedPixmapId() const +{ + if (dataSource().isEmpty() && m_data.stored()) { + //not db-aware + return m_data.id(); + } + return 0; +} + +void KexiDBImageBox::setStoredPixmapId(uint id) +{ + setData(KexiBLOBBuffer::self()->objectForId( id, /*stored*/true )); + repaint(); +} + +bool KexiDBImageBox::hasScaledContents() const +{ + return m_scaledContents; +// return m_pixmapLabel->hasScaledContents(); +} + +/*void KexiDBImageBox::setPixmap(const QByteArray& pixmap) +{ + setValueInternal(pixmap, true); +// setBackgroundMode(pixmap.isNull() ? Qt::NoBackground : Qt::PaletteBackground); +}*/ + +void KexiDBImageBox::setScaledContents(bool set) +{ +//todo m_pixmapLabel->setScaledContents(set); + m_scaledContents = set; + repaint(); +} + +void KexiDBImageBox::setKeepAspectRatio(bool set) +{ + m_keepAspectRatio = set; + if (m_scaledContents) + repaint(); +} + +QWidget* KexiDBImageBox::widget() +{ + //! @todo +// return m_pixmapLabel; + return this; +} + +bool KexiDBImageBox::cursorAtStart() +{ + return true; +} + +bool KexiDBImageBox::cursorAtEnd() +{ + return true; +} + +QByteArray KexiDBImageBox::data() const +{ + if (dataSource().isEmpty()) { + //static mode + return m_data.data(); + } + else { + //db-aware mode + return m_value; + } +} + +void KexiDBImageBox::insertFromFile() +{ + m_popupMenu->insertFromFile(); +} + +void KexiDBImageBox::handleInsertFromFileAction(const KURL& url) +{ + if (!dataSource().isEmpty() && isReadOnly()) + return; + + if (dataSource().isEmpty()) { + //static mode + KexiBLOBBuffer::Handle h = KexiBLOBBuffer::self()->insertPixmap( url ); + if (!h) + return; + setData(h); + repaint(); + } + else { + //db-aware + QString fileName( url.isLocalFile() ? url.path() : url.prettyURL() ); + + //! @todo download the file if remote, then set fileName properly + QFile f(fileName); + if (!f.open(IO_ReadOnly)) { + //! @todo err msg + return; + } + QByteArray ba = f.readAll(); + if (f.status()!=IO_Ok) { + //! @todo err msg + f.close(); + return; + } + m_valueMimeType = KImageIO::mimeType( fileName ); + setValueInternal( ba, true ); + } + +//! @todo emit signal for setting "dirty" flag within the design + if (!dataSource().isEmpty()) { + signalValueChanged(); + } +} + +void KexiDBImageBox::handleAboutToSaveAsAction(QString& origFilename, QString& fileExtension, bool& dataIsEmpty) +{ + if (data().isEmpty()) { + kdWarning() << "KexiDBImageBox::handleAboutToSaveAs(): no pixmap!" << endl; + dataIsEmpty = false; + return; + } + if (dataSource().isEmpty()) { //for static images filename and mimetype can be available + origFilename = m_data.originalFileName(); + if (!origFilename.isEmpty()) + origFilename = QString("/") + origFilename; + if (!m_data.mimeType().isEmpty()) + fileExtension = KImageIO::typeForMime(m_data.mimeType()).lower(); + } +} + +void KexiDBImageBox::handleSaveAsAction(const QString& fileName) +{ + QFile f(fileName); + if (!f.open(IO_WriteOnly)) { + //! @todo err msg + return; + } + f.writeBlock( data() ); + if (f.status()!=IO_Ok) { + //! @todo err msg + f.close(); + return; + } + f.close(); +} + +void KexiDBImageBox::handleCutAction() +{ + if (!dataSource().isEmpty() && isReadOnly()) + return; + handleCopyAction(); + clear(); +} + +void KexiDBImageBox::handleCopyAction() +{ + qApp->clipboard()->setPixmap(pixmap(), QClipboard::Clipboard); +} + +void KexiDBImageBox::handlePasteAction() +{ + if (isReadOnly() || (!m_designMode && !hasFocus())) + return; + QPixmap pm( qApp->clipboard()->pixmap(QClipboard::Clipboard) ); +// if (!pm.isNull()) +// setValueInternal(pm, true); + if (dataSource().isEmpty()) { + //static mode + setData(KexiBLOBBuffer::self()->insertPixmap( pm )); + } + else { + //db-aware mode + m_pixmap = pm; + QByteArray ba; + QBuffer buffer( ba ); + buffer.open( IO_WriteOnly ); + if (m_pixmap.save( &buffer, "PNG" )) {// write pixmap into ba in PNG format + setValueInternal( ba, true, false/* !loadPixmap */ ); + } + else { + setValueInternal( QByteArray(), true ); + } + } + + repaint(); + if (!dataSource().isEmpty()) { +// emit pixmapChanged(); + signalValueChanged(); + } +} + +void KexiDBImageBox::clear() +{ + if (dataSource().isEmpty()) { + //static mode + setData(KexiBLOBBuffer::Handle()); + } + else { + if (isReadOnly()) + return; + //db-aware mode + setValueInternal(QByteArray(), true); + //m_pixmap = QPixmap(); + } + +// m_originalFileName = QString::null; + + //! @todo emit signal for setting "dirty" flag within the design + +// m_pixmap = QPixmap(); //will be loaded on demand + repaint(); + if (!dataSource().isEmpty()) { +// emit pixmapChanged();//valueChanged(data()); + signalValueChanged(); + } +} + +void KexiDBImageBox::handleShowPropertiesAction() +{ + //! @todo +} + +void KexiDBImageBox::slotUpdateActionsAvailabilityRequested(bool& valueIsNull, bool& valueIsReadOnly) +{ + valueIsNull = !( + (dataSource().isEmpty() && !pixmap().isNull()) /*static pixmap available*/ + || (!dataSource().isEmpty() && !this->valueIsNull()) /*db-aware pixmap available*/ + ); + // read-only if static pixmap or db-aware pixmap for read-only widget: + valueIsReadOnly = !m_designMode && dataSource().isEmpty() || !dataSource().isEmpty() && isReadOnly() + || m_designMode && !dataSource().isEmpty(); +} + +/* +void KexiDBImageBox::slotAboutToHidePopupMenu() +{ +// kexipluginsdbg << "##### slotAboutToHidePopupMenu() " << endl; + m_clickTimer.start(50, true); + if (m_chooser && m_chooser->isOn()) { + m_chooser->toggle(); + if (m_setFocusOnButtonAfterClosingPopup) { + m_setFocusOnButtonAfterClosingPopup = false; + m_chooser->setFocus(); + } + } +}*/ + +void KexiDBImageBox::contextMenuEvent( QContextMenuEvent * e ) +{ + if (popupMenuAvailable()) + m_popupMenu->exec( e->globalPos(), -1 ); +} + +/*void KexiDBImageBox::slotChooserPressed() +{ +// if (!m_clickTimer.isActive()) +// return; +// m_chooser->setDown( false ); +} + +void KexiDBImageBox::slotChooserReleased() +{ +} + +void KexiDBImageBox::slotToggled(bool on) +{ + return; + +// kexipluginsdbg << "##### slotToggled() " << on << endl; + if (m_clickTimer.isActive() || !on) { + m_chooser->disableMousePress = true; + return; + } + m_chooser->disableMousePress = false; + QRect screen = qApp->desktop()->availableGeometry( m_chooser ); + QPoint p; + if ( QApplication::reverseLayout() ) { + if ( (mapToGlobal( m_chooser->rect().bottomLeft() ).y() + m_popupMenu->sizeHint().height()) <= screen.height() ) + p = m_chooser->mapToGlobal( m_chooser->rect().bottomRight() ); + else + p = m_chooser->mapToGlobal( m_chooser->rect().topRight() - QPoint( 0, m_popupMenu->sizeHint().height() ) ); + p.rx() -= m_popupMenu->sizeHint().width(); + } + else { + if ( (m_chooser->mapToGlobal( m_chooser->rect().bottomLeft() ).y() + m_popupMenu->sizeHint().height()) <= screen.height() ) + p = m_chooser->mapToGlobal( m_chooser->rect().bottomLeft() ); + else + p = m_chooser->mapToGlobal( m_chooser->rect().topLeft() - QPoint( 0, m_popupMenu->sizeHint().height() ) ); + } + if (!m_popupMenu->isVisible() && on) { + m_popupMenu->exec( p, -1 ); + m_popupMenu->setFocus(); + } + //m_chooser->setDown( false ); +}*/ + +void KexiDBImageBox::updateActionStrings() +{ + if (!m_popupMenu) + return; + if (m_designMode) { +/* QString titleString( i18n("Image Box") ); + if (!dataSource().isEmpty()) + titleString.prepend(dataSource() + " : "); + m_popupMenu->changeTitle(m_popupMenu->idAt(0), m_popupMenu->titlePixmap(m_popupMenu->idAt(0)), titleString);*/ + } + else { + //update title in data view mode, based on the data source + if (columnInfo()) { + KexiImageContextMenu::updateTitle( m_popupMenu, columnInfo()->captionOrAliasOrName(), + KexiFormPart::library()->iconName(className()) ); + } + } + + if (m_chooser) { + if (popupMenuAvailable() && dataSource().isEmpty()) { //this may work in the future (see @todo below) + QToolTip::add(m_chooser, i18n("Click to show actions for this image box")); + } else { + QString beautifiedImageBoxName; + if (m_designMode) { + beautifiedImageBoxName = dataSource(); + } + else { + beautifiedImageBoxName = columnInfo() ? columnInfo()->captionOrAliasOrName() : QString::null; + /*! @todo look at makeFirstCharacterUpperCaseInCaptions setting [bool] + (see doc/dev/settings.txt) */ + beautifiedImageBoxName = beautifiedImageBoxName[0].upper() + beautifiedImageBoxName.mid(1); + } + QToolTip::add(m_chooser, i18n("Click to show actions for \"%1\" image box").arg(beautifiedImageBoxName)); + } + } +} + +bool KexiDBImageBox::popupMenuAvailable() +{ +/*! @todo add kexi-global setting which anyway, allows to show this button + (read-only actions like copy/save as/print can be available) */ + //chooser button can be only visible when data source is specified + return !dataSource().isEmpty(); +} + +void KexiDBImageBox::setDataSource( const QString &ds ) +{ + KexiFormDataItemInterface::setDataSource( ds ); + setData(KexiBLOBBuffer::Handle()); + updateActionStrings(); + KexiFrame::setFocusPolicy( focusPolicy() ); //set modified policy + + if (m_chooser) { + m_chooser->setEnabled(popupMenuAvailable()); + if (m_dropDownButtonVisible && popupMenuAvailable()) { + m_chooser->show(); + } + else { + m_chooser->hide(); + } + } + + // update some properties s not changed by user +//! @todo get default line width from global style settings + if (!m_lineWidthChanged) { + KexiFrame::setLineWidth( ds.isEmpty() ? 0 : 1 ); + } + if (!m_paletteBackgroundColorChanged && parentWidget()) { + KexiFrame::setPaletteBackgroundColor( + dataSource().isEmpty() ? parentWidget()->paletteBackgroundColor() : palette().active().base() ); + } +} + +QSize KexiDBImageBox::sizeHint() const +{ + if (pixmap().isNull()) + return QSize(80, 80); + return pixmap().size(); +} + +int KexiDBImageBox::realLineWidth() const +{ + if (frameShape()==QFrame::Box && (frameShadow()==QFrame::Sunken || frameShadow()==QFrame::Raised)) + return 2 * lineWidth(); + else + return lineWidth(); +} + +void KexiDBImageBox::paintEvent( QPaintEvent *pe ) +{ + if (!m_paintEventEnabled) + return; + QPainter p(this); + p.setClipRect(pe->rect()); + const int m = realLineWidth() + margin(); + QColor bg(eraseColor()); + if (m_designMode && pixmap().isNull()) { + QPixmap pm(size()-QSize(m, m)); + QPainter p2; + p2.begin(&pm, this); + p2.fillRect(0,0,width(),height(), bg); + + updatePixmap(); + QPixmap *imagBoxPm; + const bool tooLarge = (height()-m-m) <= KexiDBImageBox_pm->height(); + if (tooLarge || (width()-m-m) <= KexiDBImageBox_pm->width()) + imagBoxPm = KexiDBImageBox_pmSmall; + else + imagBoxPm = KexiDBImageBox_pm; + QImage img(imagBoxPm->convertToImage()); + img = KImageEffect::flatten(img, bg.dark(150), + qGray( bg.rgb() ) <= 20 ? QColor(Qt::gray).dark(150) : bg.light(105)); + + QPixmap converted; + converted.convertFromImage(img); +// if (tooLarge) +// p2.drawPixmap(2, 2, converted); +// else + p2.drawPixmap(2, height()-m-m-imagBoxPm->height()-2, converted); + QFont f(qApp->font()); + p2.setFont(f); + p2.setPen( KexiUtils::contrastColor( bg ) ); + p2.drawText(pm.rect(), Qt::AlignCenter, + dataSource().isEmpty() + ? QString::fromLatin1(name())+"\n"+i18n("Unbound Image Box", "(unbound)") //i18n("No Image") + : dataSource()); + p2.end(); + bitBlt(this, m, m, &pm); + } + else { + QSize internalSize(size()); + if (m_chooser && m_dropDownButtonVisible && !dataSource().isEmpty()) + internalSize.setWidth( internalSize.width() - m_chooser->width() ); + + //clearing needed here because we may need to draw a pixmap with transparency + p.fillRect(0,0,width(),height(), bg); + + KexiUtils::drawPixmap( p, m, QRect(QPoint(0,0), internalSize), pixmap(), m_alignment, + m_scaledContents, m_keepAspectRatio ); + } + KexiFrame::drawFrame( &p ); + + // if the widget is focused, draw focus indicator rect _if_ there is no chooser button + if (!m_designMode && !dataSource().isEmpty() && hasFocus() && (!m_chooser || !m_chooser->isVisible())) { + style().drawPrimitive( + QStyle::PE_FocusRect, &p, style().subRect(QStyle::SR_PushButtonContents, this), + palette().active() ); + } +} + +/* virtual void KexiDBImageBox::paletteChange ( const QPalette & oldPalette ) +{ + QFrame::paletteChange(oldPalette); + if (oldPalette.active().background()!=palette().active().background()) { + delete KexiDBImageBox_pm; + KexiDBImageBox_pm = 0; + repaint(); + } +}*/ + +void KexiDBImageBox::updatePixmap() +{ + if (! (m_designMode && pixmap().isNull()) ) + return; + + if (!KexiDBImageBox_pm) { + QString fname( locate("data", QString("kexi/pics/imagebox.png")) ); + KexiDBImageBox_pmDeleter.setObject( KexiDBImageBox_pm, new QPixmap(fname, "PNG") ); + QImage img(KexiDBImageBox_pm->convertToImage()); + KexiDBImageBox_pmSmallDeleter.setObject( KexiDBImageBox_pmSmall, + new QPixmap( img.smoothScale(img.width()/2, img.height()/2, QImage::ScaleMin) ) ); + } +} + +void KexiDBImageBox::setAlignment(int alignment) +{ + m_alignment = alignment; + if (!m_scaledContents || m_keepAspectRatio) + repaint(); +} + +void KexiDBImageBox::setData(const KexiBLOBBuffer::Handle& handle) +{ + if (m_insideSetData) //avoid recursion + return; + m_insideSetData = true; + m_data = handle; + emit idChanged(handle.id()); + m_insideSetData = false; + update(); +} + +void KexiDBImageBox::resizeEvent( QResizeEvent * e ) +{ + KexiFrame::resizeEvent(e); + if (m_chooser) { + QSize s( m_chooser->sizeHint() ); + QSize margin( realLineWidth(), realLineWidth() ); + s.setHeight( height() - 2*margin.height() ); + s = s.boundedTo( size()-2*margin ); + m_chooser->resize( s ); + m_chooser->move( QRect(QPoint(0,0), e->size() - m_chooser->size() - margin + QSize(1,1)).bottomRight() ); + } +} + +/* +bool KexiDBImageBox::setProperty( const char * name, const QVariant & value ) +{ + const bool ret = QLabel::setProperty(name, value); + if (p_shadowEnabled) { + if (0==qstrcmp("indent", name) || 0==qstrcmp("font", name) || 0==qstrcmp("margin", name) + || 0==qstrcmp("frameShadow", name) || 0==qstrcmp("frameShape", name) + || 0==qstrcmp("frameStyle", name) || 0==qstrcmp("midLineWidth", name) + || 0==qstrcmp("lineWidth", name)) { + p_privateLabel->setProperty(name, value); + updatePixmap(); + } + } + return ret; +} +*/ + +void KexiDBImageBox::setColumnInfo(KexiDB::QueryColumnInfo* cinfo) +{ + KexiFormDataItemInterface::setColumnInfo(cinfo); + //updating strings and title is needed + updateActionStrings(); +} + +bool KexiDBImageBox::keyPressed(QKeyEvent *ke) +{ + // Esc key should close the popup + if (ke->state() == Qt::NoButton && ke->key() == Qt::Key_Escape) { + if (m_popupMenu->isVisible()) { + m_setFocusOnButtonAfterClosingPopup = true; + return true; + } + } +// else if (ke->state() == Qt::ControlButton && KStdAccel::shortcut(KStdAccel::Copy).keyCodeQt() == (ke->key()|Qt::CTRL)) { +// } + return false; +} + +void KexiDBImageBox::setLineWidth( int width ) +{ + m_lineWidthChanged = true; + KexiFrame::setLineWidth(width); +} + +void KexiDBImageBox::setPalette( const QPalette &pal ) +{ + KexiFrame::setPalette(pal); + if (m_insideSetPalette) + return; + m_insideSetPalette = true; + setPaletteBackgroundColor(pal.active().base()); + setPaletteForegroundColor(pal.active().foreground()); + m_insideSetPalette = false; +} + +void KexiDBImageBox::setPaletteBackgroundColor( const QColor & color ) +{ + kexipluginsdbg << "KexiDBImageBox::setPaletteBackgroundColor(): " << color.name() << endl; + m_paletteBackgroundColorChanged = true; + KexiFrame::setPaletteBackgroundColor(color); + if (m_chooser) + m_chooser->setPalette( qApp->palette() ); +} + +bool KexiDBImageBox::dropDownButtonVisible() const +{ + return m_dropDownButtonVisible; +} + +void KexiDBImageBox::setDropDownButtonVisible( bool set ) +{ +//! @todo use global default setting for this property + if (m_dropDownButtonVisible == set) + return; + m_dropDownButtonVisible = set; + if (m_chooser) { + if (m_dropDownButtonVisible) + m_chooser->show(); + else + m_chooser->hide(); + } +} + +bool KexiDBImageBox::subwidgetStretchRequired(KexiDBAutoField* autoField) const +{ + Q_UNUSED(autoField); + return true; +} + +bool KexiDBImageBox::eventFilter( QObject * watched, QEvent * e ) +{ + if (watched==this || watched==m_chooser) { //we're watching chooser as well because it's a focus proxy even if invisible + if (e->type()==QEvent::FocusIn || e->type()==QEvent::FocusOut || e->type()==QEvent::MouseButtonPress) { + update(); //to repaint focus rect + } + } + // hide popup menu as soon as it loses focus + if (watched==m_popupMenu && e->type()==QEvent::FocusOut) { + m_popupMenu->hide(); + } + return KexiFrame::eventFilter(watched, e); +} + +QWidget::FocusPolicy KexiDBImageBox::focusPolicy() const +{ + if (dataSource().isEmpty()) + return NoFocus; + return m_focusPolicyInternal; +} + +QWidget::FocusPolicy KexiDBImageBox::focusPolicyInternal() const +{ + return m_focusPolicyInternal; +} + +void KexiDBImageBox::setFocusPolicy( FocusPolicy policy ) +{ + m_focusPolicyInternal = policy; + KexiFrame::setFocusPolicy( focusPolicy() ); //set modified policy +} + +#include "kexidbimagebox.moc" diff --git a/kexi/plugins/forms/widgets/kexidbimagebox.h b/kexi/plugins/forms/widgets/kexidbimagebox.h new file mode 100644 index 00000000..3ad2f710 --- /dev/null +++ b/kexi/plugins/forms/widgets/kexidbimagebox.h @@ -0,0 +1,275 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr> + Copyright (C) 2004-2007 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 KexiDBImageBox_H +#define KexiDBImageBox_H + +#include "kexiformdataiteminterface.h" +#include "kexiframe.h" +#include "kexidbutils.h" +#include <kexiblobbuffer.h> + +class KexiDropDownButton; +class KexiImageContextMenu; + +//! @short A data-aware, editable image box. +/*! Can also act as a normal static image box. +*/ +class KEXIFORMUTILS_EXPORT KexiDBImageBox : + public KexiFrame, + public KexiFormDataItemInterface, + public KexiSubwidgetInterface +{ + Q_OBJECT + Q_PROPERTY( QString dataSource READ dataSource WRITE setDataSource ) + Q_PROPERTY( QCString dataSourceMimeType READ dataSourceMimeType WRITE setDataSourceMimeType ) + Q_PROPERTY( bool readOnly READ isReadOnly WRITE setReadOnly ) +// Q_PROPERTY( QPixmap pixmap READ pixmap WRITE setPixmap ) +// Q_PROPERTY( QByteArray pixmapData READ pixmapData WRITE setPixmapData ) + Q_PROPERTY( uint pixmapId READ pixmapId WRITE setPixmapId DESIGNABLE true STORED false ) + Q_PROPERTY( uint storedPixmapId READ storedPixmapId WRITE setStoredPixmapId DESIGNABLE false STORED true ) + Q_PROPERTY( bool scaledContents READ hasScaledContents WRITE setScaledContents ) + Q_PROPERTY( bool keepAspectRatio READ keepAspectRatio WRITE setKeepAspectRatio ) + Q_PROPERTY( Alignment alignment READ alignment WRITE setAlignment ) +// Q_PROPERTY( QString originalFileName READ originalFileName WRITE setOriginalFileName DESIGNABLE false ) +// Q_OVERRIDE( FocusPolicy focusPolicy READ focusPolicy WRITE setFocusPolicy ) + Q_PROPERTY( bool dropDownButtonVisible READ dropDownButtonVisible WRITE setDropDownButtonVisible ) + Q_OVERRIDE( int lineWidth READ lineWidth WRITE setLineWidth ) + Q_OVERRIDE( FocusPolicy focusPolicy READ focusPolicyInternal WRITE setFocusPolicy ) + + public: + KexiDBImageBox( bool designMode, QWidget *parent, const char *name = 0 ); + virtual ~KexiDBImageBox(); + + inline QString dataSource() const { return KexiFormDataItemInterface::dataSource(); } + inline QCString dataSourceMimeType() const { return KexiFormDataItemInterface::dataSourceMimeType(); } + + virtual QVariant value(); // { return m_value.data(); } + +// QByteArray pixmapData() const { return m_value.data(); } + + QPixmap pixmap() const; + + uint pixmapId() const; + + uint storedPixmapId() const; +// + virtual void setInvalidState( const QString& displayText ); + + virtual bool valueIsNull(); + + virtual bool valueIsEmpty(); + + virtual QWidget* widget(); + + //! always true + virtual bool cursorAtStart(); + + //! always true + virtual bool cursorAtEnd(); + +// //! used to catch setIndent(), etc. +// virtual bool setProperty ( const char * name, const QVariant & value ); + + virtual bool isReadOnly() const; + + bool hasScaledContents() const; + +// bool designMode() const { return m_designMode; } + + int alignment() const { return m_alignment; } + + bool keepAspectRatio() const { return m_keepAspectRatio; } + + virtual QSize sizeHint() const; + + KexiImageContextMenu *contextMenu() const; + + /*! \return original file name of image loaded from a file. + This can be later reused for displaying the image within a collection (to be implemented) + or on saving the image data back to file. */ +//todo QString originalFileName() const { return m_value.originalFileName(); } + + //! Reimplemented to override behaviour of "lineWidth" property. + virtual void setLineWidth( int width ); + + //! Reimplemented to override behaviour of "paletteBackgroundColor" + //! and "paletteForegroundColor" properties. + virtual void setPalette( const QPalette &pal ); + + //! Reimplemented to override behaviour of "paletteBackgroundColor" property. + virtual void setPaletteBackgroundColor( const QColor & color ); + + //! \return true id drop down button should be visible (the default). + bool dropDownButtonVisible() const; + + //! For overridden property + int lineWidth() const { return KexiFrame::lineWidth(); } + + /*! Overriden to change the policy behaviour a bit: + NoFocus is returned regardless the real focus flag + if the data source is empty (see dataSource()). */ + FocusPolicy focusPolicy() const; + + //! \return the internal focus policy value, i.e. the one unrelated to data source presence. + FocusPolicy focusPolicyInternal() const; + + /*! Sets the internal focus policy value. + "Internal" means that if there is no data source set, real policy becomes NoFocus. */ + virtual void setFocusPolicy( FocusPolicy policy ); + + public slots: + void setPixmapId(uint id); + + void setStoredPixmapId(uint id); + + //! Sets the datasource to \a ds + virtual void setDataSource( const QString &ds ); + + inline void setDataSourceMimeType(const QCString &ds) { KexiFormDataItemInterface::setDataSourceMimeType(ds); } + + virtual void setReadOnly(bool set); + + //! Sets \a pixmapData data for this widget. If the widget has data source set, + //! the pixmap will be also placed inside of the buffer and saved later. +//todo void setPixmapData(const QByteArray& pixmapData) { m_value.setData(pixmapData); } + + /*! Sets original file name of image loaded from a file. + @see originalFileName() */ +//todo void setOriginalFileName(const QString& name) { m_value.setOriginalFileName(name); } + + void setScaledContents(bool set); + + void setAlignment(int alignment); + + void setKeepAspectRatio(bool set); + +// void updateActionsAvailability(); + + //! @internal +// void slotToggled( bool on ); + + //! \return sets dropDownButtonVisible property. @see dropDownButtonVisible() + void setDropDownButtonVisible( bool set ); + + //! Forces execution of "insert from file" action + void insertFromFile(); + + signals: + //! Used for db-aware mode. Emitted when value has been changed. + //! Actual value can be obtained using value(). +// virtual void pixmapChanged(); +// virtual void valueChanged(const QByteArray& data); + + void idChanged(long id); + + protected slots: + void slotUpdateActionsAvailabilityRequested(bool& valueIsNull, bool& valueIsReadOnly); + + void handleInsertFromFileAction(const KURL& url); + void handleAboutToSaveAsAction(QString& origFilename, QString& fileExtension, bool& dataIsEmpty); + void handleSaveAsAction(const QString& fileName); + void handleCutAction(); + void handleCopyAction(); + void handlePasteAction(); + virtual void clear(); + void handleShowPropertiesAction(); + + protected: + //! \return data depending on the current mode (db-aware or static) + QByteArray data() const; + + virtual void contextMenuEvent ( QContextMenuEvent * e ); +// virtual void mousePressEvent( QMouseEvent *e ); + virtual void setColumnInfo(KexiDB::QueryColumnInfo* cinfo); + virtual void paintEvent( QPaintEvent* ); + virtual void resizeEvent( QResizeEvent* e ); + virtual bool eventFilter( QObject * watched, QEvent * e ); + + //! Sets value \a value for a widget. + virtual void setValueInternal( const QVariant& add, bool removeOld ) { + setValueInternal( add, removeOld, true /*loadPixmap*/ ); + } + + //! @internal, added \a loadPixmap option used by paste(). + void setValueInternal( const QVariant& add, bool removeOld, bool loadPixmap ); + + //! Updates i18n'd action strings after datasource change + void updateActionStrings(); + void updatePixmap(); + + //! @internal + void setData(const KexiBLOBBuffer::Handle& handle); + + bool popupMenuAvailable(); + + /*! Called by top-level form on key press event. + Used for Key_Escape to if the popup is visible, + so the key press won't be consumed to perform "cancel editing". */ + virtual bool keyPressed(QKeyEvent *ke); + + //! \return real line width, i.e. for Boxed sunken or Boxed raised + //! frames returns doubled width value. + int realLineWidth() const; + + //! Implemented for KexiSubwidgetInterface + virtual bool subwidgetStretchRequired(KexiDBAutoField* autoField) const; + +// virtual void drawContents ( QPainter *p ); + +// virtual void fontChange( const QFont& font ); +// virtual void styleChange( QStyle& style ); +// virtual void enabledChange( bool enabled ); + +// virtual void paletteChange( const QPalette& pal ); +// virtual void frameChanged(); +// virtual void showEvent( QShowEvent* e ); + +// void updatePixmapLater(); +// class ImageLabel; +// ImageLabel *m_pixmapLabel; + QPixmap m_pixmap; + QByteArray m_value; //!< for db-aware mode + QString m_valueMimeType; //!< for db-aware mode +// PixmapData m_value; + KexiBLOBBuffer::Handle m_data; +// QString m_originalFileName; + KexiDropDownButton *m_chooser; + KexiImageContextMenu *m_popupMenu; +//moved KActionCollection m_actionCollection; +//moved KAction *m_insertFromFileAction, *m_saveAsAction, *m_cutAction, *m_copyAction, *m_pasteAction, +// *m_deleteAction, *m_propertiesAction; +// QTimer m_clickTimer; + int m_alignment; + FocusPolicy m_focusPolicyInternal; //!< Used for focusPolicyInternal() + bool m_designMode : 1; + bool m_readOnly : 1; + bool m_scaledContents : 1; + bool m_keepAspectRatio : 1; + bool m_insideSetData : 1; + bool m_setFocusOnButtonAfterClosingPopup : 1; + bool m_lineWidthChanged : 1; + bool m_paletteBackgroundColorChanged : 1; + bool m_paintEventEnabled : 1; //!< used to disable paintEvent() + bool m_dropDownButtonVisible : 1; + bool m_insideSetPalette : 1; +}; + +#endif diff --git a/kexi/plugins/forms/widgets/kexidbintspinbox.cpp b/kexi/plugins/forms/widgets/kexidbintspinbox.cpp new file mode 100644 index 00000000..ac923347 --- /dev/null +++ b/kexi/plugins/forms/widgets/kexidbintspinbox.cpp @@ -0,0 +1,114 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr> + Copyright (C) 2004-2005 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 "kexidbintspinbox.h" + +#include <qlineedit.h> +#include <knumvalidator.h> + +KexiDBIntSpinBox::KexiDBIntSpinBox(QWidget *parent, const char *name) + : KIntSpinBox(parent, name) , KexiFormDataItemInterface() +{ + connect(this, SIGNAL(valueChanged(int)), this, SLOT(slotValueChanged())); +} + +KexiDBIntSpinBox::~KexiDBIntSpinBox() +{ +} + +void KexiDBIntSpinBox::setInvalidState( const QString& displayText ) +{ + m_invalidState = true; + setEnabled(false); + setReadOnly(true); +//! @todo move this to KexiDataItemInterface::setInvalidStateInternal() ? + if (focusPolicy() & TabFocus) + setFocusPolicy(QWidget::ClickFocus); + setSpecialValueText(displayText); + KIntSpinBox::setValue(minValue()); +} + +void +KexiDBIntSpinBox::setEnabled(bool enabled) +{ + // prevent the user from reenabling the widget when it is in invalid state + if(enabled && m_invalidState) + return; + KIntSpinBox::setEnabled(enabled); +} + +void KexiDBIntSpinBox::setValueInternal(const QVariant&, bool) +{ + KIntSpinBox::setValue(m_origValue.toInt()); +} + +QVariant +KexiDBIntSpinBox::value() +{ + return KIntSpinBox::value(); +} + +void KexiDBIntSpinBox::slotValueChanged() +{ + signalValueChanged(); +} + +bool KexiDBIntSpinBox::valueIsNull() +{ + return cleanText().isEmpty(); +} + +bool KexiDBIntSpinBox::valueIsEmpty() +{ + return false; +} + +bool KexiDBIntSpinBox::isReadOnly() const +{ + return editor()->isReadOnly(); +} + +void KexiDBIntSpinBox::setReadOnly(bool set) +{ + editor()->setReadOnly(set); +} + +QWidget* +KexiDBIntSpinBox::widget() +{ + return this; +} + +bool KexiDBIntSpinBox::cursorAtStart() +{ + return false; //! \todo ? +} + +bool KexiDBIntSpinBox::cursorAtEnd() +{ + return false; //! \todo ? +} + +void KexiDBIntSpinBox::clear() +{ + KIntSpinBox::setValue(minValue()); //! \todo ? +} + +#include "kexidbintspinbox.moc" diff --git a/kexi/plugins/forms/widgets/kexidbintspinbox.h b/kexi/plugins/forms/widgets/kexidbintspinbox.h new file mode 100644 index 00000000..cddc614e --- /dev/null +++ b/kexi/plugins/forms/widgets/kexidbintspinbox.h @@ -0,0 +1,80 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr> + Copyright (C) 2004-2005 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 KexiDBIntSpinBox_H +#define KexiDBIntSpinBox_H + +#include "kexiformdataiteminterface.h" +#include <qwidget.h> +#include <knuminput.h> + +//! @short A db-aware int spin box +class KEXIFORMUTILS_EXPORT KexiDBIntSpinBox : public KIntSpinBox, public KexiFormDataItemInterface +{ + Q_OBJECT + Q_PROPERTY(QString dataSource READ dataSource WRITE setDataSource DESIGNABLE true) + Q_PROPERTY(QCString dataSourceMimeType READ dataSourceMimeType WRITE setDataSourceMimeType DESIGNABLE true) + Q_PROPERTY( bool readOnly READ isReadOnly WRITE setReadOnly DESIGNABLE true ) + + public: + KexiDBIntSpinBox(QWidget *parent, const char *name=0); + virtual ~KexiDBIntSpinBox(); + + inline QString dataSource() const { return KexiFormDataItemInterface::dataSource(); } + inline QCString dataSourceMimeType() const { return KexiFormDataItemInterface::dataSourceMimeType(); } + virtual QVariant value(); + virtual void setInvalidState( const QString& displayText ); + + //! \return true if editor's value is null (not empty) + //! Used for checking if a given constraint within table of form is met. + virtual bool valueIsNull(); + + //! \return true if editor's value is empty (not necessary null). + //! Only few data types can accept "EMPTY" property + //! (use KexiDB::Field::hasEmptyProperty() to check this). + //! Used for checking if a given constraint within table or form is met. + virtual bool valueIsEmpty(); + + /*! \return 'readOnly' flag for this widget. */ + virtual bool isReadOnly() const; + + /*! \return the view widget of this item, e.g. line edit widget. */ + virtual QWidget* widget(); + + virtual bool cursorAtStart(); + virtual bool cursorAtEnd(); + virtual void clear(); + + virtual void setEnabled(bool enabled); + + public slots: + inline void setDataSource(const QString &ds) { KexiFormDataItemInterface::setDataSource(ds); } + inline void setDataSourceMimeType(const QCString &ds) { KexiFormDataItemInterface::setDataSourceMimeType(ds); } + void slotValueChanged(); + virtual void setReadOnly(bool set); + + protected: + virtual void setValueInternal(const QVariant& add, bool removeOld); + + private: + bool m_invalidState : 1; +}; + +#endif diff --git a/kexi/plugins/forms/widgets/kexidblabel.cpp b/kexi/plugins/forms/widgets/kexidblabel.cpp new file mode 100644 index 00000000..e30cc19e --- /dev/null +++ b/kexi/plugins/forms/widgets/kexidblabel.cpp @@ -0,0 +1,650 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Christian Nitschkowski <segfault_ii@web.de> + Copyright (C) 2005 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 "kexidblabel.h" + +#include <qbitmap.h> +#include <qpainter.h> +#include <qdrawutil.h> +#include <qapplication.h> +#include <qtimer.h> + +#include <kdebug.h> +#include <kimageeffect.h> + +#include <kexidb/field.h> +#include <kexiutils/utils.h> + +#define SHADOW_OFFSET_X 3 +#define SHADOW_OFFSET_Y 3 +#define SHADOW_FACTOR 16.0 +#define SHADOW_OPACITY 50.0 +#define SHADOW_AXIS_FACTOR 2.0 +#define SHADOW_DIAGONAL_FACTOR 1.0 +#define SHADOW_THICKNESS 1 + +//! @internal +class KexiDBInternalLabel : public QLabel { + friend class KexiDBLabel; + public: + KexiDBInternalLabel( KexiDBLabel* ); + virtual ~KexiDBInternalLabel(); + + protected: + void updateFrame(); + + QImage makeShadow( const QImage& textImage, const QColor &bgColor, const QRect& boundingRect ); + QRect getBounding( const QImage &image, const QRect& startRect ); +// double defaultDecay( QImage& source, int i, int j ); + KPixmap getShadowPixmap(); + + QRect m_shadowRect; + KexiDBLabel *m_parentLabel; +}; + +KexiDBInternalLabel::KexiDBInternalLabel( KexiDBLabel* parent ) + : QLabel( parent ) + , m_parentLabel(parent) +{ + int a = alignment() | Qt::WordBreak; + a &= (0xffffff ^ Qt::AlignVertical_Mask); + a |= Qt::AlignTop; + setAlignment( a ); + updateFrame(); +} + +void KexiDBInternalLabel::updateFrame() +{ + setIndent(m_parentLabel->indent()); + setMargin(m_parentLabel->margin()); + setFont(m_parentLabel->font()); + + setFrameShadow(m_parentLabel->frameShadow()); + setFrameShape(m_parentLabel->frameShape()); + setFrameStyle(m_parentLabel->frameStyle()); + setMidLineWidth(m_parentLabel->midLineWidth()); + setLineWidth(m_parentLabel->lineWidth()); +} + +KexiDBInternalLabel::~KexiDBInternalLabel() +{ +} + +/*! +* This method is copied from kdebase/kdesktop/kshadowengine.cpp +* Some modifactions were made. +* -- +* Christian Nitschkowski +*/ +QImage KexiDBInternalLabel::makeShadow( const QImage& textImage, + const QColor &bgColor, const QRect& boundingRect ) +{ + QImage result; + QString origText( text() ); + + // create a new image for for the shaddow + const int w = textImage.width(); + const int h = textImage.height(); + + // avoid calling these methods for every pixel + const int bgRed = bgColor.red(); + const int bgGreen = bgColor.green(); + const int bgBlue = bgColor.blue(); + + const int startX = boundingRect.x() + SHADOW_THICKNESS; + const int startY = boundingRect.y() + SHADOW_THICKNESS; + const int effectWidth = boundingRect.bottomRight().x() - SHADOW_THICKNESS; + const int effectHeight = boundingRect.bottomRight().y() - SHADOW_THICKNESS; +// const int period = (effectWidth - startX) / 10; + + double alphaShadow; + + /* + * This is the source pixmap + */ + QImage img = textImage.convertDepth( 32 ); + + /* + * Resize the image if necessary + */ + if ( ( result.width() != w ) || ( result.height() != h ) ) { + result.create( w, h, 32 ); + } + +// result.fill( 0 ); // all black + double realOpacity = SHADOW_OPACITY + QMIN(50.0/double(256.0-qGray(bgColor.rgb())), 50.0); + //int _h, _s, _v; + //.getHsv( &_h, &_s, &_v ); + if (colorGroup().background()==Qt::red)//_s>=250 && _v>=250) //for colors like cyan or red, make the result more white + realOpacity += 50.0; + result.fill( (int)realOpacity ); + result.setAlphaBuffer( true ); + + for ( int i = startX; i < effectWidth; i++ ) { + for ( int j = startY; j < effectHeight; j++ ) { + /*! + * This method is copied from kdebase/kdesktop/kshadowengine.cpp + * Some modifactions were made. + * -- + * Christian Nitschkowski + */ + if ( ( i < 1 ) || ( j < 1 ) || ( i > img.width() - 2 ) || ( j > img.height() - 2 ) ) + continue; + else + alphaShadow = ( qGray( img.pixel( i - 1, j - 1 ) ) * SHADOW_DIAGONAL_FACTOR + + qGray( img.pixel( i - 1, j ) ) * SHADOW_AXIS_FACTOR + + qGray( img.pixel( i - 1, j + 1 ) ) * SHADOW_DIAGONAL_FACTOR + + qGray( img.pixel( i , j - 1 ) ) * SHADOW_AXIS_FACTOR + + 0 + + qGray( img.pixel( i , j + 1 ) ) * SHADOW_AXIS_FACTOR + + qGray( img.pixel( i + 1, j - 1 ) ) * SHADOW_DIAGONAL_FACTOR + + qGray( img.pixel( i + 1, j ) ) * SHADOW_AXIS_FACTOR + + qGray( img.pixel( i + 1, j + 1 ) ) * SHADOW_DIAGONAL_FACTOR ) / SHADOW_FACTOR; + + // update the shadow's i,j pixel. + if (alphaShadow > 0) + result.setPixel( i, j, qRgba( bgRed, bgGreen , bgBlue, + ( int ) (( alphaShadow > realOpacity ) ? realOpacity : alphaShadow) + ) ); + } +/*caused too much redraw problems if (period && i % period) { + qApp->processEvents(); + if (text() != origText) //text has been changed in the meantime: abort + return QImage(); + }*/ + } + return result; +} + +KPixmap KexiDBInternalLabel::getShadowPixmap() { + /*! + * Backup the default color used to draw text. + */ + const QColor textColor = colorGroup().foreground(); + + /*! + * Temporary storage for the generated shadow + */ + KPixmap finalPixmap, tempPixmap; + QImage shadowImage, tempImage; + QPainter painter; + + m_shadowRect = QRect(); + + tempPixmap.resize( size() ); + tempPixmap.fill( Qt::black ); + tempPixmap.setMask( tempPixmap.createHeuristicMask( true ) ); + + /*! + * The textcolor has to be white for creating shadows! + */ + setPaletteForegroundColor( Qt::white ); + + /*! + Draw the label "as usual" in a pixmap + */ + painter.begin( &tempPixmap ); + painter.setFont( font() ); + drawContents( &painter ); + painter.end(); + setPaletteForegroundColor( textColor ); + + /*! + * Calculate the first bounding rect. + * This will fit around the unmodified text. + */ + shadowImage = tempPixmap; + tempPixmap.setMask( QBitmap() ); + + /*! + Get the first bounding rect. + This may speed up makeShadow later. + */ + m_shadowRect = getBounding( shadowImage, m_shadowRect ); + + /*! + * Enlarge the bounding rect to make sure the shadow + * will fit in. + * The new rect has to fit in the pixmap. + * I have to admit this isn't really nice code... + */ + m_shadowRect.setX( QMAX( m_shadowRect.x() - ( m_shadowRect.width() / 4 ), 0 ) ); + m_shadowRect.setY( QMAX( m_shadowRect.y() - ( m_shadowRect.height() / 4 ), 0 ) ); + m_shadowRect.setBottomRight( QPoint( + QMIN( m_shadowRect.x() + ( m_shadowRect.width() * 3 / 2 ), shadowImage.width() ), + QMIN( m_shadowRect.y() + ( m_shadowRect.height() * 3 / 2 ), shadowImage.height() ) ) ); + + shadowImage = makeShadow( shadowImage, + qGray( colorGroup().background().rgb() ) < 127 ? Qt::white : Qt::black, + m_shadowRect ); + if (shadowImage.isNull()) + return KPixmap(); + + /*! + Now get the final bounding rect. + */ + m_shadowRect = getBounding( shadowImage, m_shadowRect ); + + /*! + Paint the labels background in a new pixmap. + */ + finalPixmap.resize( size() ); + painter.begin( &finalPixmap ); + painter.fillRect( 0, 0, finalPixmap.width(), finalPixmap.height(), + palette().brush( + isEnabled() ? QPalette::Active : QPalette::Disabled, + QColorGroup::Background ) ); + painter.end(); + + /*! + Copy the part of the background the shadow will be on + to another pixmap. + */ + tempPixmap.resize( m_shadowRect.size() ); + if (!finalPixmap.isNull()) { + bitBlt( &tempPixmap, 0, 0, &finalPixmap, + m_shadowRect.x() + SHADOW_OFFSET_X, + m_shadowRect.y() + SHADOW_OFFSET_Y, + m_shadowRect.width(), + m_shadowRect.height() ); + } + /*! + Replace the big background pixmap with the + part we could out just before. + */ + finalPixmap = tempPixmap; + + /*! + Copy the "interesting" part of the shadow image + to a new image. + I tried to copy this to a pixmap directly, + but it didn't work correctly. + Maybe a Qt bug? + */ + tempImage = shadowImage.copy( m_shadowRect ); + tempPixmap.convertFromImage( tempImage ); + /*! + Anyways, merge the shadow with the background. + */ + if (!tempPixmap.isNull()) { + bitBlt( &finalPixmap, 0, 0, &tempPixmap ); + } + + /** + Now move the rect. + Don't do this before the shadow is copied from shadowImage! + */ + m_shadowRect.moveBy( SHADOW_OFFSET_X, SHADOW_OFFSET_Y ); + + return finalPixmap; +} + +QRect KexiDBInternalLabel::getBounding( const QImage &image, const QRect& startRect ) { + QPoint topLeft; + QPoint bottomRight; + + const int startX = startRect.x(); + const int startY = startRect.y(); + /*! + * Ugly beast to get the correct width and height + */ + const int width = QMIN( ( startRect.bottomRight().x() > 0 + ? startRect.bottomRight().x() : QCOORD_MAX ), + image.width() ); + const int height = QMIN( ( startRect.bottomRight().y() > 0 + ? startRect.bottomRight().y() : QCOORD_MAX ), + image.height() ); + + /*! + Assume the first pixel has the color of the + background that has to be cut away. + Qt uses the four corner pixels to guess the + correct color, but in this case the topleft + pixel should be enough. + */ + QRgb trans = image.pixel( 0, 0 ); + + for ( int y = startY; y < height; y++ ) { + for ( int x = startX; x < width; x++ ) { + if ( image.pixel( x, y ) != trans ) { + topLeft.setY( y ); + y = height; + break; + } + } + } + + for ( int x = startX; x < width; x++ ) { + for ( int y = startY; y < height; y++ ) { + if ( image.pixel( x, y ) != trans ) { + topLeft.setX( x ); + x = width; + break; + } + } + } + + for ( int y = height - 1; y > topLeft.y(); y-- ) { + for ( int x = width - 1; x > topLeft.x(); x-- ) { + if ( image.pixel( x, y ) != trans ) { + bottomRight.setY( y + 1 ); + y = 0; + break; + } + } + } + + for ( int x = width - 1; x > topLeft.x(); x-- ) { + for ( int y = height - 1; y > topLeft.y(); y-- ) { + if ( image.pixel( x, y ) != trans ) { + bottomRight.setX( x + 1 ); + x = 0; + break; + } + } + } + + return QRect( + topLeft.x(), + topLeft.y(), + bottomRight.x() - topLeft.x(), + bottomRight.y() - topLeft.y() ); +} + +//========================================================= + +//! @internal +class KexiDBLabel::Private +{ + public: + Private() + : timer(0) +// , autonumberDisplayParameters(0) + , pixmapDirty( true ) + , shadowEnabled( false ) + , resizeEvent( false ) + { + } + ~Private() {} + KPixmap shadowPixmap; + QPoint shadowPosition; + KexiDBInternalLabel* internalLabel; + QTimer* timer; + QColor frameColor; + bool pixmapDirty : 1; + bool shadowEnabled : 1; + bool resizeEvent : 1; +}; + +//========================================================= + +KexiDBLabel::KexiDBLabel( QWidget *parent, const char *name, WFlags f ) + : QLabel( parent, name, f ) + , KexiDBTextWidgetInterface() + , KexiFormDataItemInterface() + , d( new Private() ) +{ + init(); +} + +KexiDBLabel::KexiDBLabel( const QString& text, QWidget *parent, const char *name, WFlags f ) + : QLabel( parent, name, f ) + , KexiDBTextWidgetInterface() + , KexiFormDataItemInterface() + , d( new Private() ) +{ + init(); + setText( text ); +} + +KexiDBLabel::~KexiDBLabel() +{ + delete d; +} + +void KexiDBLabel::init() +{ + m_hasFocusableWidget = false; + d->internalLabel = new KexiDBInternalLabel( this ); + d->internalLabel->hide(); + d->frameColor = palette().active().foreground(); + + setAlignment( d->internalLabel->alignment() ); +} + +void KexiDBLabel::updatePixmapLater() { + if (d->resizeEvent) { + if (!d->timer) { + d->timer = new QTimer(this, "KexiDBLabelTimer"); + connect(d->timer, SIGNAL(timeout()), this, SLOT(updatePixmap())); + } + d->timer->start(100, true); + d->resizeEvent = false; + return; + } + if (d->timer && d->timer->isActive()) + return; + updatePixmap(); +} + +void KexiDBLabel::updatePixmap() { + /*! + Whatever has changed in KexiDBLabel, + every parameter is set to our private-label. + Just in case... + */ + d->internalLabel->setText( text() ); + d->internalLabel->setFixedSize( size() ); + d->internalLabel->setPalette( palette() ); + d->internalLabel->setAlignment( alignment() ); +// d->shadowPixmap = KPixmap(); //parallel repaints won't hurt us cause incomplete pixmap + KPixmap shadowPixmap = d->internalLabel->getShadowPixmap(); + if (shadowPixmap.isNull()) + return; + d->shadowPixmap = shadowPixmap; + d->shadowPosition = d->internalLabel->m_shadowRect.topLeft(); + d->pixmapDirty = false; + repaint(); +} + +void KexiDBLabel::paintEvent( QPaintEvent* e ) +{ + QPainter p( this ); + if ( d->shadowEnabled ) { + /*! + If required, update the pixmap-cache. + */ + if ( d->pixmapDirty ) { + updatePixmapLater(); + } + + /*! + If the part that should be redrawn intersects with our shadow, + redraw the shadow where it intersects with e->rect(). + Have to move the clipping rect around a bit because + the shadow has to be drawn using an offset relative to + the widgets border. + */ + if ( !d->pixmapDirty && e->rect().contains( d->shadowPosition ) && !d->shadowPixmap.isNull()) { + QRect clipRect = QRect( + QMAX( e->rect().x() - d->shadowPosition.x(), 0 ), + QMAX( e->rect().y() - d->shadowPosition.y(), 0 ), + QMIN( e->rect().width() + d->shadowPosition.x(), d->shadowPixmap.width() ), + QMIN( e->rect().height() + d->shadowPosition.y(), d->shadowPixmap.height() ) ); + p.drawPixmap( d->internalLabel->m_shadowRect.topLeft(), d->shadowPixmap, clipRect ); + } + } + KexiDBTextWidgetInterface::paint( this, &p, text().isEmpty(), alignment(), false ); + QLabel::paintEvent( e ); +} + +void KexiDBLabel::setValueInternal( const QVariant& add, bool removeOld ) { + if (removeOld) + setText(add.toString()); + else + setText( m_origValue.toString() + add.toString() ); +} + +QVariant KexiDBLabel::value() { + return text(); +} + +void KexiDBLabel::setInvalidState( const QString& displayText ) +{ + setText( displayText ); +} + +bool KexiDBLabel::valueIsNull() +{ + return text().isNull(); +} + +bool KexiDBLabel::valueIsEmpty() +{ + return text().isEmpty(); +} + +bool KexiDBLabel::isReadOnly() const +{ + return true; +} + +void KexiDBLabel::setReadOnly( bool readOnly ) +{ + Q_UNUSED(readOnly); +} + +QWidget* KexiDBLabel::widget() +{ + return this; +} + +bool KexiDBLabel::cursorAtStart() +{ + return false; +} + +bool KexiDBLabel::cursorAtEnd() +{ + return false; +} + +void KexiDBLabel::clear() +{ + setText(QString::null); +} + +bool KexiDBLabel::setProperty( const char * name, const QVariant & value ) +{ + const bool ret = QLabel::setProperty(name, value); + if (d->shadowEnabled) { + if (0==qstrcmp("indent", name) || 0==qstrcmp("font", name) || 0==qstrcmp("margin", name) + || 0==qstrcmp("frameShadow", name) || 0==qstrcmp("frameShape", name) + || 0==qstrcmp("frameStyle", name) || 0==qstrcmp("midLineWidth", name) + || 0==qstrcmp("lineWidth", name)) { + d->internalLabel->setProperty(name, value); + updatePixmap(); + } + } + return ret; +} + +void KexiDBLabel::setColumnInfo(KexiDB::QueryColumnInfo* cinfo) +{ + KexiFormDataItemInterface::setColumnInfo(cinfo); + KexiDBTextWidgetInterface::setColumnInfo(cinfo, this); +} + +void KexiDBLabel::setShadowEnabled( bool state ) { + d->shadowEnabled = state; + d->pixmapDirty = true; + if (state) + d->internalLabel->updateFrame(); + repaint(); +} + +void KexiDBLabel::resizeEvent( QResizeEvent* e ) { + if (isVisible()) + d->resizeEvent = true; + d->pixmapDirty = true; + QLabel::resizeEvent( e ); +} + +void KexiDBLabel::fontChange( const QFont& font ) { + d->pixmapDirty = true; + d->internalLabel->setFont( font ); + QLabel::fontChange( font ); +} + +void KexiDBLabel::styleChange( QStyle& style ) { + d->pixmapDirty = true; + QLabel::styleChange( style ); +} + +void KexiDBLabel::enabledChange( bool enabled ) { + d->pixmapDirty = true; + d->internalLabel->setEnabled( enabled ); + QLabel::enabledChange( enabled ); +} + +void KexiDBLabel::paletteChange( const QPalette& oldPal ) { + Q_UNUSED(oldPal); + d->pixmapDirty = true; + d->internalLabel->setPalette( palette() ); +} + +/*const QColor & KexiDBLabel::paletteForegroundColor () const +{ + return d->foregroundColor; +} + +void KexiDBLabel::setPaletteForegroundColor ( const QColor& color ) +{ + d->foregroundColor = color; +}*/ + +void KexiDBLabel::frameChanged() { + d->pixmapDirty = true; + d->internalLabel->updateFrame(); + QFrame::frameChanged(); +} + +void KexiDBLabel::showEvent( QShowEvent* e ) { + d->pixmapDirty = true; + QLabel::showEvent( e ); +} + +void KexiDBLabel::setText( const QString& text ) { + d->pixmapDirty = true; + QLabel::setText( text ); + //This is necessary for KexiFormDataItemInterface + valueChanged(); + repaint(); +} + +bool KexiDBLabel::shadowEnabled() const +{ + return d->shadowEnabled; +} + +#define ClassName KexiDBLabel +#define SuperClassName QLabel +#include "kexiframeutils_p.cpp" +#include "kexidblabel.moc" diff --git a/kexi/plugins/forms/widgets/kexidblabel.h b/kexi/plugins/forms/widgets/kexidblabel.h new file mode 100644 index 00000000..ec4e626a --- /dev/null +++ b/kexi/plugins/forms/widgets/kexidblabel.h @@ -0,0 +1,140 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Christian Nitschkowski <segfault_ii@web.de> + Copyright (C) 2005 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 KEXIDBLABEL_H +#define KEXIDBLABEL_H + +#include <qimage.h> +#include <qlabel.h> + +#include <kpixmap.h> + +#include "../kexiformdataiteminterface.h" +#include "../kexidbtextwidgetinterface.h" +#include <widget/utils/kexidisplayutils.h> + +class QPainter; +class QTimer; +class KexiDBInternalLabel; + +//! @short An extended, data-aware, read-only text label. +/*! It's text may have a drop-shadow. + + @author Christian Nitschkowski, Jaroslaw Staniek +*/ +class KEXIFORMUTILS_EXPORT KexiDBLabel : public QLabel, protected KexiDBTextWidgetInterface, public KexiFormDataItemInterface { + Q_OBJECT + Q_PROPERTY( QString dataSource READ dataSource WRITE setDataSource DESIGNABLE true ) + Q_PROPERTY( QCString dataSourceMimeType READ dataSourceMimeType WRITE setDataSourceMimeType DESIGNABLE true ) + Q_PROPERTY( bool shadowEnabled READ shadowEnabled WRITE setShadowEnabled DESIGNABLE true ) + Q_OVERRIDE( QPixmap pixmap DESIGNABLE false ) + Q_OVERRIDE( bool scaledContents DESIGNABLE false ) +// Q_OVERRIDE( QColor paletteForegroundColor READ paletteForegroundColor WRITE setPaletteForegroundColor DESIGNABLE true ) + Q_PROPERTY( QColor frameColor READ frameColor WRITE setFrameColor DESIGNABLE true ) + + public: + KexiDBLabel( QWidget *parent, const char *name = 0, WFlags f = 0 ); + KexiDBLabel( const QString& text, QWidget *parent, const char *name = 0, WFlags f = 0 ); + virtual ~KexiDBLabel(); + + inline QString dataSource() const { return KexiFormDataItemInterface::dataSource(); } + inline QCString dataSourceMimeType() const { return KexiFormDataItemInterface::dataSourceMimeType(); } + + virtual QVariant value(); + + bool shadowEnabled() const; + + virtual void setInvalidState( const QString& displayText ); + + virtual bool valueIsNull(); + + virtual bool valueIsEmpty(); + + //! always true + virtual bool isReadOnly() const; + + virtual QWidget* widget(); + + //! always false + virtual bool cursorAtStart(); + + //! always false + virtual bool cursorAtEnd(); + + virtual void clear(); + + //! used to catch setIndent(), etc. + virtual bool setProperty ( const char * name, const QVariant & value ); + + virtual const QColor& frameColor() const; + +// const QColor & paletteForegroundColor() const; + + public slots: + //! Sets the datasource to \a ds + inline void setDataSource( const QString &ds ) { KexiFormDataItemInterface::setDataSource( ds ); } + + inline void setDataSourceMimeType(const QCString &ds) { KexiFormDataItemInterface::setDataSourceMimeType(ds); } + + virtual void setText( const QString& text ); + + /*! Enable/Disable the shadow effect. + KexiDBLabel acts just like a normal QLabel when shadow is disabled. */ + void setShadowEnabled( bool state ); + + virtual void setPalette( const QPalette &pal ); + + virtual void setFrameColor(const QColor& color); + +// void setPaletteForegroundColor( const QColor& color ); + + protected slots: + //! empty + virtual void setReadOnly( bool readOnly ); + void updatePixmap(); + + protected: + void init(); + virtual void setColumnInfo(KexiDB::QueryColumnInfo* cinfo); + virtual void paintEvent( QPaintEvent* ); + virtual void resizeEvent( QResizeEvent* e ); + + //! Sets value \a value for a widget. + virtual void setValueInternal( const QVariant& add, bool removeOld ); + + virtual void fontChange( const QFont& font ); + virtual void styleChange( QStyle& style ); + virtual void enabledChange( bool enabled ); + + virtual void paletteChange( const QPalette& oldPal ); + virtual void frameChanged(); + virtual void showEvent( QShowEvent* e ); + + //! Reimplemented to paint using real frame color instead of froeground. + //! Also allows to paint more types of frame. + virtual void drawFrame( QPainter * ); + + void updatePixmapLater(); + + class Private; + Private *d; +}; + +#endif diff --git a/kexi/plugins/forms/widgets/kexidblineedit.cpp b/kexi/plugins/forms/widgets/kexidblineedit.cpp new file mode 100644 index 00000000..3897a8cb --- /dev/null +++ b/kexi/plugins/forms/widgets/kexidblineedit.cpp @@ -0,0 +1,417 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr> + Copyright (C) 2004-2007 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 "kexidblineedit.h" +#include "kexidbautofield.h" + +#include <kdebug.h> +#include <knumvalidator.h> +#include <kdatetbl.h> + +#include <qpopupmenu.h> +#include <qpainter.h> + +#include <kexiutils/utils.h> +#include <kexidb/queryschema.h> +#include <kexidb/fieldvalidator.h> +#include <kexiutils/utils.h> + +//! @todo reenable as an app aption +//#define USE_KLineEdit_setReadOnly + +//! @internal A validator used for read only flag to disable editing +class KexiDBLineEdit_ReadOnlyValidator : public QValidator +{ + public: + KexiDBLineEdit_ReadOnlyValidator( QObject * parent ) + : QValidator(parent) + { + } + ~KexiDBLineEdit_ReadOnlyValidator() {} + virtual State validate( QString &, int & ) const { return Invalid; } +}; + +//----- + +KexiDBLineEdit::KexiDBLineEdit(QWidget *parent, const char *name) + : KLineEdit(parent, name) + , KexiDBTextWidgetInterface() + , KexiFormDataItemInterface() +//moved , m_dateFormatter(0) +//moved , m_timeFormatter(0) + , m_menuExtender(this, this) + , m_internalReadOnly(false) + , m_slotTextChanged_enabled(true) +{ +#ifdef USE_KLineEdit_setReadOnly +//! @todo reenable as an app aption + QPalette p(widget->palette()); + p.setColor( lighterGrayBackgroundColor(palette()) ); + widget->setPalette(p); +#endif + + connect(this, SIGNAL(textChanged(const QString&)), this, SLOT(slotTextChanged(const QString&))); +} + +KexiDBLineEdit::~KexiDBLineEdit() +{ +//moved delete m_dateFormatter; +//moved delete m_timeFormatter; +} + +void KexiDBLineEdit::setInvalidState( const QString& displayText ) +{ + KLineEdit::setReadOnly(true); +//! @todo move this to KexiDataItemInterface::setInvalidStateInternal() ? + if (focusPolicy() & TabFocus) + setFocusPolicy(QWidget::ClickFocus); + setText(displayText); +} + +void KexiDBLineEdit::setValueInternal(const QVariant& add, bool removeOld) +{ +#if 0 //moved to KexiTextFormatter + QVariant value; + if (removeOld) + value = add; + else { + if (add.toString().isEmpty()) + value = m_origValue; + else + value = m_origValue.toString() + add.toString(); + } + + if (m_columnInfo) { + const KexiDB::Field::Type t = m_columnInfo->field->type(); + if (t == KexiDB::Field::Boolean) { + //! @todo temporary solution for booleans! + setText( value.toBool() ? "1" : "0" ); + return; + } + else if (t == KexiDB::Field::Date) { + setText( dateFormatter()->dateToString( value.toString().isEmpty() ? QDate() : value.toDate() ) ); + setCursorPosition(0); //ok? + return; + } + else if (t == KexiDB::Field::Time) { + setText( + timeFormatter()->timeToString( + //hack to avoid converting null variant to valid QTime(0,0,0) + value.toString().isEmpty() ? value.toTime() : QTime(99,0,0) + ) + ); + setCursorPosition(0); //ok? + return; + } + else if (t == KexiDB::Field::DateTime) { + if (value.toString().isEmpty() ) { + setText( QString::null ); + } + else { + setText( + dateFormatter()->dateToString( value.toDateTime().date() ) + " " + + timeFormatter()->timeToString( value.toDateTime().time() ) + ); + } + setCursorPosition(0); //ok? + return; + } + } +#endif + m_slotTextChanged_enabled = false; + setText( m_textFormatter.valueToText(removeOld ? QVariant() : m_origValue, add.toString()) ); +// setText( value.toString() ); + setCursorPosition(0); //ok? + m_slotTextChanged_enabled = true; +} + +QVariant KexiDBLineEdit::value() +{ + return m_textFormatter.textToValue( text() ); +#if 0 // moved to KexiTextFormatter + if (! m_columnInfo) + return QVariant(); + const KexiDB::Field::Type t = m_columnInfo->field->type(); + switch (t) { + case KexiDB::Field::Text: + case KexiDB::Field::LongText: + return text(); + case KexiDB::Field::Byte: + case KexiDB::Field::ShortInteger: + return text().toShort(); +//! @todo uint, etc? + case KexiDB::Field::Integer: + return text().toInt(); + case KexiDB::Field::BigInteger: + return text().toLongLong(); + case KexiDB::Field::Boolean: + //! @todo temporary solution for booleans! + return text() == "1" ? QVariant(true,1) : QVariant(false,0); + case KexiDB::Field::Date: + return dateFormatter()->stringToVariant( text() ); + case KexiDB::Field::Time: + return timeFormatter()->stringToVariant( text() ); + case KexiDB::Field::DateTime: + return stringToDateTime(*dateFormatter(), *timeFormatter(), text()); + case KexiDB::Field::Float: + return text().toFloat(); + case KexiDB::Field::Double: + return text().toDouble(); + default: + return QVariant(); + } +//! @todo more data types! + return text(); +#endif +} + +void KexiDBLineEdit::slotTextChanged(const QString&) +{ + if (!m_slotTextChanged_enabled) + return; + signalValueChanged(); +} + +bool KexiDBLineEdit::valueIsNull() +{ + return valueIsEmpty(); //ok??? text().isNull(); +} + +bool KexiDBLineEdit::valueIsEmpty() +{ + return m_textFormatter.valueIsEmpty( text() ); +#if 0 // moved to KexiTextFormatter + if (text().isEmpty()) + return true; + + if (m_columnInfo) { + const KexiDB::Field::Type t = m_columnInfo->field->type(); + if (t == KexiDB::Field::Date || ) + return dateFormatter()->isEmpty( text() ); + else if (t == KexiDB::Field::Time) + return timeFormatter()->isEmpty( text() ); + else if (t == KexiDB::Field::Time) + return dateTimeIsEmpty( *dateFormatter(), *timeFormatter(), text() ); + } + +//! @todo + return text().isEmpty(); +#endif +} + +bool KexiDBLineEdit::valueIsValid() +{ + return m_textFormatter.valueIsValid( text() ); +#if 0 // moved to KexiTextFormatter + if (!m_columnInfo) + return true; +//! @todo fix for fields with "required" property = true + if (valueIsEmpty()/*ok?*/) + return true; + + const KexiDB::Field::Type t = m_columnInfo->field->type(); + if (t == KexiDB::Field::Date) + return dateFormatter()->stringToVariant( text() ).isValid(); + else if (t == KexiDB::Field::Time) + return timeFormatter()->stringToVariant( text() ).isValid(); + else if (t == KexiDB::Field::DateTime) + return dateTimeIsValid( *dateFormatter(), *timeFormatter(), text() ); + +//! @todo + return true; +#endif +} + +bool KexiDBLineEdit::isReadOnly() const +{ + return m_internalReadOnly; +} + +void KexiDBLineEdit::setReadOnly( bool readOnly ) +{ +#ifdef USE_KLineEdit_setReadOnly +//! @todo reenable as an app aption + return KLineEdit::setReadOnly( readOnly ); +#else + m_internalReadOnly = readOnly; + if (m_internalReadOnly) { + m_readWriteValidator = validator(); + if (!m_readOnlyValidator) + m_readOnlyValidator = new KexiDBLineEdit_ReadOnlyValidator(this); + setValidator( m_readOnlyValidator ); + } + else { + //revert to r/w validator + setValidator( m_readWriteValidator ); + } + m_menuExtender.updatePopupMenuActions(); +#endif +} + +QPopupMenu * KexiDBLineEdit::createPopupMenu() +{ + QPopupMenu *contextMenu = KLineEdit::createPopupMenu(); + m_menuExtender.createTitle(contextMenu); + return contextMenu; +} + + +QWidget* KexiDBLineEdit::widget() +{ + return this; +} + +bool KexiDBLineEdit::cursorAtStart() +{ + return cursorPosition()==0; +} + +bool KexiDBLineEdit::cursorAtEnd() +{ + return cursorPosition()==(int)text().length(); +} + +void KexiDBLineEdit::clear() +{ + if (!m_internalReadOnly) + KLineEdit::clear(); +} + + +void KexiDBLineEdit::setColumnInfo(KexiDB::QueryColumnInfo* cinfo) +{ + KexiFormDataItemInterface::setColumnInfo(cinfo); + m_textFormatter.setField( cinfo ? cinfo->field : 0 ); + + if (!cinfo) + return; + +//! @todo handle input mask (via QLineEdit::setInputMask()) using a special KexiDB::FieldInputMask class + setValidator( new KexiDB::FieldValidator(*cinfo->field, this) ); + +#if 0 // moved to KexiTextFormatter + if (t==KexiDB::Field::Date) { +//! @todo use KDateWidget? + setInputMask( dateFormatter()->inputMask() ); + } + else if (t==KexiDB::Field::Time) { +//! @todo use KTimeWidget +// setInputMask("00:00:00"); + setInputMask( timeFormatter()->inputMask() ); + } + else if (t==KexiDB::Field::DateTime) { + setInputMask( + dateTimeInputMask( *dateFormatter(), *timeFormatter() ) ); + } +#endif + const QString inputMask( m_textFormatter.inputMask() ); + if (!inputMask.isEmpty()) + setInputMask( inputMask ); + + KexiDBTextWidgetInterface::setColumnInfo(cinfo, this); +} + +/*todo +void KexiDBLineEdit::paint( QPainter *p ) +{ + KexiDBTextWidgetInterface::paint( this, &p, text().isEmpty(), alignment(), hasFocus() ); +}*/ + +void KexiDBLineEdit::paintEvent ( QPaintEvent *pe ) +{ + KLineEdit::paintEvent( pe ); + QPainter p(this); + KexiDBTextWidgetInterface::paint( this, &p, text().isEmpty(), alignment(), hasFocus() ); +} + +bool KexiDBLineEdit::event( QEvent * e ) +{ + const bool ret = KLineEdit::event( e ); + KexiDBTextWidgetInterface::event(e, this, text().isEmpty()); + if (e->type()==QEvent::FocusOut) { + QFocusEvent *fe = static_cast<QFocusEvent *>(e); +// if (fe->reason()!=QFocusEvent::ActiveWindow && fe->reason()!=QFocusEvent::Popup) { + if (fe->reason()==QFocusEvent::Tab || fe->reason()==QFocusEvent::Backtab) { + //display aligned to left after loosing the focus (only if this is tab/backtab event) +//! @todo add option to set cursor at the beginning + setCursorPosition(0); //ok? + } + } + return ret; +} + +bool KexiDBLineEdit::appendStretchRequired(KexiDBAutoField* autoField) const +{ + return KexiDBAutoField::Top == autoField->labelPosition(); +} + +void KexiDBLineEdit::handleAction(const QString& actionName) +{ + if (actionName=="edit_copy") { + copy(); + } + else if (actionName=="edit_paste") { + paste(); + } + else if (actionName=="edit_cut") { + cut(); + } + //! @todo ? +} + +void KexiDBLineEdit::setDisplayDefaultValue(QWidget *widget, bool displayDefaultValue) +{ + KexiFormDataItemInterface::setDisplayDefaultValue(widget, displayDefaultValue); + // initialize display parameters for default / entered value + KexiDisplayUtils::DisplayParameters * const params + = displayDefaultValue ? m_displayParametersForDefaultValue : m_displayParametersForEnteredValue; + setFont(params->font); + QPalette pal(palette()); + pal.setColor(QPalette::Active, QColorGroup::Text, params->textColor); + setPalette(pal); +} + +void KexiDBLineEdit::undo() +{ + cancelEditor(); +} + +void KexiDBLineEdit::moveCursorToEnd() +{ + KLineEdit::end(false/*!mark*/); +} + +void KexiDBLineEdit::moveCursorToStart() +{ + KLineEdit::home(false/*!mark*/); +} + +void KexiDBLineEdit::selectAll() +{ + KLineEdit::selectAll(); +} + +bool KexiDBLineEdit::keyPressed(QKeyEvent *ke) +{ + Q_UNUSED(ke); + return false; +} + +#include "kexidblineedit.moc" diff --git a/kexi/plugins/forms/widgets/kexidblineedit.h b/kexi/plugins/forms/widgets/kexidblineedit.h new file mode 100644 index 00000000..5f0262b2 --- /dev/null +++ b/kexi/plugins/forms/widgets/kexidblineedit.h @@ -0,0 +1,170 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr> + Copyright (C) 2004-2007 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 KexiDBLineEdit_H +#define KexiDBLineEdit_H + +#include <klineedit.h> +#include <qvalidator.h> + +#include "kexiformdataiteminterface.h" +#include "kexidbtextwidgetinterface.h" +#include "kexidbutils.h" +#include <widget/tableview/kexitextformatter.h> +#include <widget/utils/kexidatetimeformatter.h> + +class KexiDBWidgetContextMenuExtender; + +/*! @internal Utility: alter background color to be a blended color + of the background and base (usually lighter gray). Used for read-only mode. */ +void setLighterGrayBackgroundColor(QWidget* widget); + +//! @short Line edit widget for Kexi forms +/*! Handles many data types. User input is validated by using validators + and/or input masks. +*/ +class KEXIFORMUTILS_EXPORT KexiDBLineEdit : + public KLineEdit, + protected KexiDBTextWidgetInterface, + public KexiFormDataItemInterface, + public KexiSubwidgetInterface +{ + Q_OBJECT + Q_PROPERTY(QString dataSource READ dataSource WRITE setDataSource DESIGNABLE true) + Q_PROPERTY(QCString dataSourceMimeType READ dataSourceMimeType WRITE setDataSourceMimeType DESIGNABLE true) + Q_OVERRIDE(bool readOnly READ isReadOnly WRITE setReadOnly DESIGNABLE true) + + public: + KexiDBLineEdit(QWidget *parent, const char *name=0); + virtual ~KexiDBLineEdit(); + + inline QString dataSource() const { return KexiFormDataItemInterface::dataSource(); } + inline QCString dataSourceMimeType() const { return KexiFormDataItemInterface::dataSourceMimeType(); } + virtual QVariant value(); + virtual void setInvalidState( const QString& displayText ); + + //! \return true if editor's value is null (not empty) + //! Used for checking if a given constraint within table of form is met. + virtual bool valueIsNull(); + + //! \return true if editor's value is empty (not necessary null). + //! Only few data types can accept "EMPTY" property + //! (use KexiDB::Field::hasEmptyProperty() to check this). + //! Used for checking if a given constraint within table or form is met. + virtual bool valueIsEmpty(); + + /*! \return true if the value is valid */ + virtual bool valueIsValid(); + + /*! \return 'readOnly' flag for this widget. */ + virtual bool isReadOnly() const; + + /*! If \a displayDefaultValue is true, the value set by KexiDataItemInterface::setValue() + is displayed in a special way. Used by KexiFormDataProvider::fillDataItems(). + \a widget is equal to 'this'. + Reimplemented after KexiFormDataItemInterface. */ + virtual void setDisplayDefaultValue(QWidget* widget, bool displayDefaultValue); + + /*! \return the view widget of this item, e.g. line edit widget. */ + virtual QWidget* widget(); + + virtual bool cursorAtStart(); + virtual bool cursorAtEnd(); + virtual void clear(); + + virtual void setColumnInfo(KexiDB::QueryColumnInfo* cinfo); + + /*! Handles action having standard name \a actionName. + Action could be: "edit_copy", "edit_paste", etc. + Reimplemented after KexiDataItemChangesListener. */ + virtual void handleAction(const QString& actionName); + + /*! Called by top-level form on key press event to consume widget-specific shortcuts. */ + virtual bool keyPressed(QKeyEvent *ke); + + public slots: + inline void setDataSource(const QString &ds) { KexiFormDataItemInterface::setDataSource(ds); } + inline void setDataSourceMimeType(const QCString &ds) { KexiFormDataItemInterface::setDataSourceMimeType(ds); } + virtual void setReadOnly( bool readOnly ); + + //! Reimplemented, so "undo" means the same as "cancelEditor" action + virtual void undo(); + + //! Implemented for KexiDataItemInterface + virtual void moveCursorToEnd(); + + //! Implemented for KexiDataItemInterface + virtual void moveCursorToStart(); + + //! Implemented for KexiDataItemInterface + virtual void selectAll(); + + protected slots: + void slotTextChanged(const QString&); + + protected: + virtual void paintEvent ( QPaintEvent * ); + virtual void setValueInternal(const QVariant& add, bool removeOld); + virtual bool event ( QEvent * ); + +#if 0 +//moved to KexiTextFormatter + inline KexiDateFormatter* dateFormatter() { + return m_dateFormatter ? m_dateFormatter : m_dateFormatter = new KexiDateFormatter(); + } + + inline KexiTimeFormatter* timeFormatter() { + return m_timeFormatter ? m_timeFormatter : m_timeFormatter = new KexiTimeFormatter(); + } +#endif + + virtual QPopupMenu * createPopupMenu(); + + //! Implemented for KexiSubwidgetInterface + virtual bool appendStretchRequired(KexiDBAutoField* autoField) const; + +#if 0 +//moved to KexiTextFormatter + //! Used for date and date/time types + KexiDateFormatter* m_dateFormatter; + //! Used for time and date/time types + KexiTimeFormatter* m_timeFormatter; +#endif + //! Used to format text + KexiTextFormatter m_textFormatter; + + //! Used for read only flag to disable editing + QGuardedPtr<const QValidator> m_readOnlyValidator; + + //! Used to remember the previous validator used forf r/w mode, after setting the read only flag + QGuardedPtr<const QValidator> m_readWriteValidator; + + //! Used for extending context menu + KexiDBWidgetContextMenuExtender m_menuExtender; + + //! Used in isReadOnly, as sometimes we want to have the flag set tot true when KLineEdit::isReadOnly + //! is still false. + bool m_internalReadOnly : 1; + + //! Used in slotTextChanged() + bool m_slotTextChanged_enabled : 1; +}; + +#endif diff --git a/kexi/plugins/forms/widgets/kexidbsubform.cpp b/kexi/plugins/forms/widgets/kexidbsubform.cpp new file mode 100644 index 00000000..8d1971a9 --- /dev/null +++ b/kexi/plugins/forms/widgets/kexidbsubform.cpp @@ -0,0 +1,131 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr> + Copyright (C) 2004-2005 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 "kexidbsubform.h" + +#include "kexidbform.h" +#include "../kexiformview.h" +#include <kexidb/utils.h> +#include <formeditor/formIO.h> +#include <formeditor/objecttree.h> +#include <formeditor/utils.h> +#include <formeditor/container.h> +#include <formeditor/formmanager.h> + +KexiDBSubForm::KexiDBSubForm(KFormDesigner::Form *parentForm, QWidget *parent, const char *name) +: QScrollView(parent, name), m_parentForm(parentForm), m_form(0), m_widget(0) +{ + setFrameStyle(QFrame::WinPanel | QFrame::Sunken); + viewport()->setPaletteBackgroundColor(colorGroup().mid()); +} +/* +void +KexiDBSubForm::paintEvent(QPaintEvent *ev) +{ + QScrollView::paintEvent(ev); + QPainter p; + + setWFlags(WPaintUnclipped); + + QString txt("Subform"); + QFont f = font(); + f.setPointSize(f.pointSize() * 3); + QFontMetrics fm(f); + const int txtw = fm.width(txt), txth = fm.height(); + + p.begin(this, true); + p.setPen(black); + p.setFont(f); + p.drawText(width()/2, height()/2, txt, Qt::AlignCenter|Qt::AlignVCenter); + p.end(); + + clearWFlags( WPaintUnclipped ); +} +*/ +void +KexiDBSubForm::setFormName(const QString &name) +{ + if(m_formName==name) + return; + + m_formName = name; //assign, even if the name points to nowhere + + if(name.isEmpty()) { + delete m_widget; + m_widget = 0; + updateScrollBars(); + return; + } + + QWidget *pw = parentWidget(); + KexiFormView *view = 0; + QStringList list; + while(pw) { + if(pw->isA("KexiDBSubForm")) { + if(list.contains(pw->name())) { +//! @todo error message + return; // Be sure to don't run into a endless-loop cause of recursive subforms. + } + list.append(pw->name()); + } + else if(! view && pw->isA("KexiFormView")) + view = static_cast<KexiFormView*>(pw); // we need a KexiFormView* + pw = pw->parentWidget(); + } + + if (!view || !view->parentDialog() || !view->parentDialog()->mainWin() + || !view->parentDialog()->mainWin()->project()->dbConnection()) + return; + + KexiDB::Connection *conn = view->parentDialog()->mainWin()->project()->dbConnection(); + + // we check if there is a form with this name + int id = KexiDB::idForObjectName(*conn, name, KexiPart::FormObjectType); + if((id == 0) || (id == view->parentDialog()->id())) // == our form + return; // because of recursion when loading + + // we create the container widget + delete m_widget; + m_widget = new KexiDBFormBase(viewport(), "KexiDBSubForm_widget"); + m_widget->show(); + addChild(m_widget); + m_form = new KFormDesigner::Form(KexiFormPart::library(), this->name()); + m_form->createToplevel(m_widget); + + // and load the sub form + QString data; + tristate res = conn->loadDataBlock(id, data, QString::null); + if (res == true) + res = KFormDesigner::FormIO::loadFormFromString(m_form, m_widget, data); + if(res != true) { + delete m_widget; + m_widget = 0; + updateScrollBars(); + m_formName = QString::null; + return; + } + m_form->setDesignMode(false); + + // Install event filters on the whole newly created form + KFormDesigner::ObjectTreeItem *tree = m_parentForm->objectTree()->lookup(QObject::name()); + KFormDesigner::installRecursiveEventFilter(this, tree->eventEater()); +} + +#include "kexidbsubform.moc" diff --git a/kexi/plugins/forms/widgets/kexidbsubform.h b/kexi/plugins/forms/widgets/kexidbsubform.h new file mode 100644 index 00000000..5b73f860 --- /dev/null +++ b/kexi/plugins/forms/widgets/kexidbsubform.h @@ -0,0 +1,52 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr> + Copyright (C) 2004-2005 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 KexiDBSubForm_H +#define KexiDBSubForm_H + +#include <qscrollview.h> +#include <formeditor/form.h> + +//! @short A form embedded as a widget inside other form +class KEXIFORMUTILS_EXPORT KexiDBSubForm : public QScrollView +{ + Q_OBJECT + Q_PROPERTY(QString formName READ formName WRITE setFormName DESIGNABLE true) + + public: + KexiDBSubForm(KFormDesigner::Form *parentForm, QWidget *parent, const char *name); + ~KexiDBSubForm() {} + + //! \return the name of the subform to display inside this widget + QString formName() const { return m_formName; } + + //! Sets the name of the subform to display inside this widget + void setFormName(const QString &name); + + //void paintEvent(QPaintEvent *ev); + + private: + KFormDesigner::Form *m_parentForm; + KFormDesigner::Form *m_form; + QWidget *m_widget; + QString m_formName; +}; + +#endif diff --git a/kexi/plugins/forms/widgets/kexidbtextedit.cpp b/kexi/plugins/forms/widgets/kexidbtextedit.cpp new file mode 100644 index 00000000..8541fc01 --- /dev/null +++ b/kexi/plugins/forms/widgets/kexidbtextedit.cpp @@ -0,0 +1,209 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr> + 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 "kexidbtextedit.h" +#include "kexidblineedit.h" +#include <kexidb/queryschema.h> + +#include <kapplication.h> +#include <kstdaccel.h> +#include <kdebug.h> + +#include <qpainter.h> + +KexiDBTextEdit::KexiDBTextEdit(QWidget *parent, const char *name) + : KTextEdit(parent, name) + , KexiDBTextWidgetInterface() + , KexiFormDataItemInterface() + , m_menuExtender(this, this) + , m_slotTextChanged_enabled(true) +{ + connect(this, SIGNAL(textChanged()), this, SLOT(slotTextChanged())); + installEventFilter(this); +} + +KexiDBTextEdit::~KexiDBTextEdit() +{ +} + +void KexiDBTextEdit::setInvalidState( const QString& displayText ) +{ + setReadOnly(true); +//! @todo move this to KexiDataItemInterface::setInvalidStateInternal() ? + if (focusPolicy() & TabFocus) + setFocusPolicy(QWidget::ClickFocus); + KTextEdit::setText(displayText); +} + +void KexiDBTextEdit::setValueInternal(const QVariant& add, bool removeOld) +{ + if (m_columnInfo && m_columnInfo->field->type()==KexiDB::Field::Boolean) { +//! @todo temporary solution for booleans! + KTextEdit::setText( add.toBool() ? "1" : "0" ); + } + else { + if (removeOld) + KTextEdit::setText( add.toString() ); + else + KTextEdit::setText( m_origValue.toString() + add.toString() ); + } +} + +QVariant KexiDBTextEdit::value() +{ + return text(); +} + +void KexiDBTextEdit::slotTextChanged() +{ + if (!m_slotTextChanged_enabled) + return; + signalValueChanged(); +} + +bool KexiDBTextEdit::valueIsNull() +{ + return text().isNull(); +} + +bool KexiDBTextEdit::valueIsEmpty() +{ + return text().isEmpty(); +} + +bool KexiDBTextEdit::isReadOnly() const +{ + return KTextEdit::isReadOnly(); +} + +void KexiDBTextEdit::setReadOnly( bool readOnly ) +{ + KTextEdit::setReadOnly( readOnly ); + QPalette p = palette(); + QColor c(readOnly ? lighterGrayBackgroundColor(kapp->palette()) : p.color(QPalette::Normal, QColorGroup::Base)); + setPaper( c ); + p.setColor(QColorGroup::Base, c); + p.setColor(QColorGroup::Background, c); + setPalette( p ); +} + +void KexiDBTextEdit::setText( const QString & text, const QString & context ) +{ + KTextEdit::setText(text, context); +} + +QWidget* KexiDBTextEdit::widget() +{ + return this; +} + +bool KexiDBTextEdit::cursorAtStart() +{ + int para, index; + getCursorPosition ( ¶, &index ); + return para==0 && index==0; +} + +bool KexiDBTextEdit::cursorAtEnd() +{ + int para, index; + getCursorPosition ( ¶, &index ); + return (paragraphs()-1)==para && (paragraphLength(paragraphs()-1)-1)==index; +} + +void KexiDBTextEdit::clear() +{ + setText(QString::null, QString::null); +} + +void KexiDBTextEdit::setColumnInfo(KexiDB::QueryColumnInfo* cinfo) +{ + KexiFormDataItemInterface::setColumnInfo(cinfo); + if (!cinfo) + return; + KexiDBTextWidgetInterface::setColumnInfo(m_columnInfo, this); +} + +void KexiDBTextEdit::paintEvent ( QPaintEvent *pe ) +{ + KTextEdit::paintEvent( pe ); + QPainter p(this); + KexiDBTextWidgetInterface::paint( this, &p, text().isEmpty(), alignment(), hasFocus() ); +} + +QPopupMenu * KexiDBTextEdit::createPopupMenu(const QPoint & pos) +{ + QPopupMenu *contextMenu = KTextEdit::createPopupMenu(pos); + m_menuExtender.createTitle(contextMenu); + return contextMenu; +} + +void KexiDBTextEdit::undo() +{ + cancelEditor(); +} + +void KexiDBTextEdit::setDisplayDefaultValue(QWidget* widget, bool displayDefaultValue) +{ + KexiFormDataItemInterface::setDisplayDefaultValue(widget, displayDefaultValue); + // initialize display parameters for default / entered value + KexiDisplayUtils::DisplayParameters * const params + = displayDefaultValue ? m_displayParametersForDefaultValue : m_displayParametersForEnteredValue; + QPalette pal(palette()); + pal.setColor(QPalette::Active, QColorGroup::Text, params->textColor); + setPalette(pal); + setFont(params->font); +//! @todo support rich text... +/* m_slotTextChanged_enabled = false; + //for rich text... + const QString origText( text() ); + KTextEdit::setText(QString::null); + setCurrentFont(params->font); + setColor(params->textColor); + KTextEdit::setText(origText); + m_slotTextChanged_enabled = true;*/ +} + +void KexiDBTextEdit::moveCursorToEnd() +{ + KTextEdit::setCursorPosition(paragraphs()-1, paragraphLength( paragraphs()-1 )); +} + +void KexiDBTextEdit::moveCursorToStart() +{ + KTextEdit::setCursorPosition(0 /*para*/, 0 /*index*/); +} + +void KexiDBTextEdit::selectAll() +{ + KTextEdit::selectAll(); +} + +void KexiDBTextEdit::keyPressEvent( QKeyEvent *ke ) +{ + // for instance, Windows uses Ctrl+Tab for moving between tabs, so do not steal this shortcut + if (KStdAccel::tabNext().contains( KKey(ke) ) || KStdAccel::tabPrev().contains( KKey(ke) )) { + ke->ignore(); + return; + } + KTextEdit::keyPressEvent(ke); +} + +#include "kexidbtextedit.moc" diff --git a/kexi/plugins/forms/widgets/kexidbtextedit.h b/kexi/plugins/forms/widgets/kexidbtextedit.h new file mode 100644 index 00000000..a380b070 --- /dev/null +++ b/kexi/plugins/forms/widgets/kexidbtextedit.h @@ -0,0 +1,113 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr> + Copyright (C) 2004-2007 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 KexiDBTextEdit_H +#define KexiDBTextEdit_H + +#include "kexiformdataiteminterface.h" +#include "kexidbtextwidgetinterface.h" +#include "kexidbutils.h" +#include <ktextedit.h> + +//! @short Multiline edit widget for Kexi forms +class KEXIFORMUTILS_EXPORT KexiDBTextEdit : + public KTextEdit, + protected KexiDBTextWidgetInterface, + public KexiFormDataItemInterface +{ + Q_OBJECT + Q_PROPERTY(QString dataSource READ dataSource WRITE setDataSource DESIGNABLE true) + Q_PROPERTY(QCString dataSourceMimeType READ dataSourceMimeType WRITE setDataSourceMimeType DESIGNABLE true) + + public: + KexiDBTextEdit(QWidget *parent, const char *name=0); + virtual ~KexiDBTextEdit(); + + inline QString dataSource() const { return KexiFormDataItemInterface::dataSource(); } + inline QCString dataSourceMimeType() const { return KexiFormDataItemInterface::dataSourceMimeType(); } + virtual QVariant value(); + virtual void setInvalidState( const QString& displayText ); + + //! \return true if editor's value is null (not empty) + //! Used for checking if a given constraint within table of form is met. + virtual bool valueIsNull(); + + //! \return true if editor's value is empty (not necessary null). + //! Only few data types can accept "EMPTY" property + //! (use KexiDB::Field::hasEmptyProperty() to check this). + //! Used for checking if a given constraint within table or form is met. + virtual bool valueIsEmpty(); + + /*! \return 'readOnly' flag for this widget. */ + virtual bool isReadOnly() const; + + /*! \return the view widget of this item, e.g. line edit widget. */ + virtual QWidget* widget(); + + virtual bool cursorAtStart(); + virtual bool cursorAtEnd(); + virtual void clear(); + + virtual void setColumnInfo(KexiDB::QueryColumnInfo* cinfo); + + /*! If \a displayDefaultValue is true, the value set by KexiDataItemInterface::setValue() + is displayed in a special way. Used by KexiFormDataProvider::fillDataItems(). + \a widget is equal to 'this'. + Reimplemented after KexiFormDataItemInterface. */ + virtual void setDisplayDefaultValue(QWidget* widget, bool displayDefaultValue); + + //! Windows uses Ctrl+Tab for moving between tabs, so do not steal this shortcut + virtual void keyPressEvent( QKeyEvent *ke ); + + public slots: + inline void setDataSource(const QString &ds) { KexiFormDataItemInterface::setDataSource(ds); } + inline void setDataSourceMimeType(const QCString &ds) { KexiFormDataItemInterface::setDataSourceMimeType(ds); } + virtual void setReadOnly( bool readOnly ); + virtual void setText( const QString & text, const QString & context ); + + //! Reimplemented, so "undo" means the same as "cancelEditor" action +//! @todo enable "real" undo internally so user can use ctrl+z while editing + virtual void undo(); + + //! Implemented for KexiDataItemInterface + virtual void moveCursorToEnd(); + + //! Implemented for KexiDataItemInterface + virtual void moveCursorToStart(); + + //! Implemented for KexiDataItemInterface + virtual void selectAll(); + + protected slots: + void slotTextChanged(); + + protected: + virtual void paintEvent ( QPaintEvent * ); + virtual void setValueInternal(const QVariant& add, bool removeOld); + QPopupMenu * createPopupMenu(const QPoint & pos); + + //! Used for extending context menu + KexiDBWidgetContextMenuExtender m_menuExtender; + + //! Used to disable slotTextChanged() + bool m_slotTextChanged_enabled : 1; +}; + +#endif diff --git a/kexi/plugins/forms/widgets/kexidbtimeedit.cpp b/kexi/plugins/forms/widgets/kexidbtimeedit.cpp new file mode 100644 index 00000000..82e61b83 --- /dev/null +++ b/kexi/plugins/forms/widgets/kexidbtimeedit.cpp @@ -0,0 +1,156 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr> + Copyright (C) 2004-2005 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 "kexidbtimeedit.h" + +#include <qtoolbutton.h> +#include <qlayout.h> +#include <qpainter.h> + +#include <kpopupmenu.h> +#include <kdatepicker.h> +#include <kdatetbl.h> +#include <kexiutils/utils.h> + +KexiDBTimeEdit::KexiDBTimeEdit(const QTime &time, QWidget *parent, const char *name) + : QTimeEdit(time, parent, name), KexiFormDataItemInterface() +{ + m_invalidState = false; + setAutoAdvance(true); + m_cleared = false; + +#ifdef QDateTimeEditor_HACK + m_dte_time = KexiUtils::findFirstChild<QDateTimeEditor>(this, "QDateTimeEditor"); +#else + m_dte_time = 0; +#endif + + connect(this, SIGNAL(valueChanged(const QTime&)), this, SLOT(slotValueChanged(const QTime&))); +} + +KexiDBTimeEdit::~KexiDBTimeEdit() +{ +} + +void KexiDBTimeEdit::setInvalidState( const QString&) +{ + setEnabled(false); + setReadOnly(true); + m_invalidState = true; +//! @todo move this to KexiDataItemInterface::setInvalidStateInternal() ? + if (focusPolicy() & TabFocus) + setFocusPolicy(QWidget::ClickFocus); +} + +void +KexiDBTimeEdit::setEnabled(bool enabled) +{ + // prevent the user from reenabling the widget when it is in invalid state + if(enabled && m_invalidState) + return; + QTimeEdit::setEnabled(enabled); +} + +void KexiDBTimeEdit::setValueInternal(const QVariant &add, bool removeOld) +{ + m_cleared = !m_origValue.isValid(); + + int setNumberOnFocus = -1; + QTime t; + QString addString(add.toString()); + if (removeOld) { + if (!addString.isEmpty() && addString[0].latin1()>='0' && addString[0].latin1() <='9') { + setNumberOnFocus = addString[0].latin1()-'0'; + t = QTime(setNumberOnFocus, 0, 0); + } + } + else + t = m_origValue.toTime(); + + setTime(t); +} + +QVariant +KexiDBTimeEdit::value() +{ + //QDateTime - a hack needed because QVariant(QTime) has broken isNull() + return QVariant(QDateTime( m_cleared ? QDate() : QDate(0,1,2)/*nevermind*/, time())); +} + +bool KexiDBTimeEdit::valueIsNull() +{ + return !time().isValid() || time().isNull(); +} + +bool KexiDBTimeEdit::valueIsEmpty() +{ + return m_cleared; +} + +bool KexiDBTimeEdit::isReadOnly() const +{ + //! @todo: data/time edit API has no readonly flag, + //! so use event filter to avoid changes made by keyboard or mouse when m_readOnly==true + return m_readOnly; //!isEnabled(); +} + +void KexiDBTimeEdit::setReadOnly(bool set) +{ + m_readOnly = set; +} + +QWidget* +KexiDBTimeEdit::widget() +{ + return this; +} + +bool KexiDBTimeEdit::cursorAtStart() +{ +#ifdef QDateTimeEditor_HACK + return m_dte_time && hasFocus() && m_dte_time->focusSection()==0; +#else + return false; +#endif +} + +bool KexiDBTimeEdit::cursorAtEnd() +{ +#ifdef QDateTimeEditor_HACK + return m_dte_time && hasFocus() + && m_dte_time->focusSection()==int(m_dte_time->sectionCount()-1); +#else + return false; +#endif +} + +void KexiDBTimeEdit::clear() +{ + setTime(QTime()); + m_cleared = true; +} + +void +KexiDBTimeEdit::slotValueChanged(const QTime&) +{ + m_cleared = false; +} + +#include "kexidbtimeedit.moc" diff --git a/kexi/plugins/forms/widgets/kexidbtimeedit.h b/kexi/plugins/forms/widgets/kexidbtimeedit.h new file mode 100644 index 00000000..9665b1f9 --- /dev/null +++ b/kexi/plugins/forms/widgets/kexidbtimeedit.h @@ -0,0 +1,87 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr> + Copyright (C) 2004-2005 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 KexiDBTimeEdit_H +#define KexiDBTimeEdit_H + +#include "kexiformdataiteminterface.h" +#include "kexidbtextwidgetinterface.h" +#include <qdatetimeedit.h> + +class QDateTimeEditor; + +//! @short A db-aware time editor +class KEXIFORMUTILS_EXPORT KexiDBTimeEdit : public QTimeEdit, public KexiFormDataItemInterface +{ + Q_OBJECT + Q_PROPERTY(QString dataSource READ dataSource WRITE setDataSource DESIGNABLE true) + Q_PROPERTY(QCString dataSourceMimeType READ dataSourceMimeType WRITE setDataSourceMimeType DESIGNABLE true) + Q_PROPERTY( bool readOnly READ isReadOnly WRITE setReadOnly DESIGNABLE true ) + + public: + KexiDBTimeEdit(const QTime &time, QWidget *parent, const char *name=0); + virtual ~KexiDBTimeEdit(); + + inline QString dataSource() const { return KexiFormDataItemInterface::dataSource(); } + inline QCString dataSourceMimeType() const { return KexiFormDataItemInterface::dataSourceMimeType(); } + virtual QVariant value(); + virtual void setInvalidState( const QString& displayText ); + + //! \return true if editor's value is null (not empty) + //! Used for checking if a given constraint within table of form is met. + virtual bool valueIsNull(); + + //! \return true if editor's value is empty (not necessary null). + //! Only few data types can accept "EMPTY" property + //! (use KexiDB::Field::hasEmptyProperty() to check this). + //! Used for checking if a given constraint within table or form is met. + virtual bool valueIsEmpty(); + + /*! \return 'readOnly' flag for this widget. */ + virtual bool isReadOnly() const; + + /*! \return the view widget of this item, e.g. line edit widget. */ + virtual QWidget* widget(); + + virtual bool cursorAtStart(); + virtual bool cursorAtEnd(); + virtual void clear(); + + virtual void setEnabled(bool enabled); + + public slots: + inline void setDataSource(const QString &ds) { KexiFormDataItemInterface::setDataSource(ds); } + inline void setDataSourceMimeType(const QCString &ds) { KexiFormDataItemInterface::setDataSourceMimeType(ds); } + virtual void setReadOnly(bool set); + + protected slots: + void slotValueChanged(const QTime&); + + protected: + virtual void setValueInternal(const QVariant& add, bool removeOld); + + private: + QDateTimeEditor* m_dte_time; + bool m_invalidState : 1; + bool m_cleared : 1; + bool m_readOnly : 1; +}; + +#endif diff --git a/kexi/plugins/forms/widgets/kexidbutils.cpp b/kexi/plugins/forms/widgets/kexidbutils.cpp new file mode 100644 index 00000000..0c08d64c --- /dev/null +++ b/kexi/plugins/forms/widgets/kexidbutils.cpp @@ -0,0 +1,99 @@ +/* This file is part of the KDE project + Copyright (C) 2006-2007 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 "kexidbutils.h" + +#include <kpopupmenu.h> +#include <kiconloader.h> + +#include <kexidb/queryschema.h> +#include <kexidb/utils.h> +#include <formeditor/widgetlibrary.h> +#include <kexiutils/utils.h> +#include "../kexiformpart.h" +#include <widget/utils/kexicontextmenuutils.h> + + +QColor lighterGrayBackgroundColor(const QPalette& palette) +{ + return KexiUtils::blendedColors(palette.active().background(), palette.active().base(), 1, 2); +} + +//------- + +KexiDBWidgetContextMenuExtender::KexiDBWidgetContextMenuExtender( QObject* parent, KexiDataItemInterface* iface ) + : QObject(parent) + , m_iface(iface) + , m_contextMenuHasTitle(false) +{ +} + +KexiDBWidgetContextMenuExtender::~KexiDBWidgetContextMenuExtender() +{ +} + +void KexiDBWidgetContextMenuExtender::createTitle(QPopupMenu *menu) +{ + if (!menu) + return; + m_contextMenu = menu; + KPopupTitle *titleItem = new KPopupTitle(); + const int id = m_contextMenu->insertItem(titleItem, -1, 0); + m_contextMenu->setItemEnabled(id, false); + QString icon; + if (dynamic_cast<QWidget*>(m_iface)) + icon = KexiFormPart::library()->iconName(dynamic_cast<QWidget*>(m_iface)->className()); + + m_contextMenuHasTitle = m_iface->columnInfo() ? + KexiContextMenuUtils::updateTitle(m_contextMenu, + m_iface->columnInfo()->captionOrAliasOrName(), + KexiDB::simplifiedTypeName(*m_iface->columnInfo()->field), icon) + : false; + + if (!m_contextMenuHasTitle) + m_contextMenu->removeItem(id); + updatePopupMenuActions(); +} + +void KexiDBWidgetContextMenuExtender::updatePopupMenuActions() +{ + if (m_contextMenu) { + enum { IdUndo, IdRedo, IdSep1, IdCut, IdCopy, IdPaste, IdClear, IdSep2, IdSelectAll }; //from qlineedit.h + const bool readOnly = m_iface->isReadOnly(); + const int id = m_contextMenu->idAt(m_contextMenuHasTitle ? 1 : 0); + +//! @todo maybe redo will be enabled one day? + m_contextMenu->removeItem(id-(int)IdRedo); + + // update cut/copy/paste + m_contextMenu->setItemEnabled(id-(int)IdCut, !readOnly); + m_contextMenu->setItemEnabled(id-(int)IdPaste, !readOnly); + m_contextMenu->setItemEnabled(id-(int)IdClear, !readOnly); + } +} + +//------------------ + +KexiSubwidgetInterface::KexiSubwidgetInterface() +{ +} + +KexiSubwidgetInterface::~KexiSubwidgetInterface() +{ +} diff --git a/kexi/plugins/forms/widgets/kexidbutils.h b/kexi/plugins/forms/widgets/kexidbutils.h new file mode 100644 index 00000000..386f1ee5 --- /dev/null +++ b/kexi/plugins/forms/widgets/kexidbutils.h @@ -0,0 +1,71 @@ +/* This file is part of the KDE project + Copyright (C) 2006-2007 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 KDBWIDGETS_UTILS_H +#define KDBWIDGETS_UTILS_H + +#include <qpopupmenu.h> +#include <kexidataiteminterface.h> + +QColor lighterGrayBackgroundColor(const QPalette& palette); + +//! @short Used for extending editor widgets' context menu. +/*! @internal This is performed by adding a title and disabling editing + actions when "read only" flag is true. */ +class KexiDBWidgetContextMenuExtender : public QObject +{ + public: + KexiDBWidgetContextMenuExtender( QObject* parent, KexiDataItemInterface* iface ); + ~KexiDBWidgetContextMenuExtender(); + + //! Creates title for context menu \a menu + void createTitle(QPopupMenu *menu); + + //! Enables or disables context menu actions that can modify the value. + //! The menu has to be previously provided by createTitle(). + void updatePopupMenuActions(); + + /*! Updates title for context menu based on data item \a iface caption or name + Used in createTitle(QPopupMenu *menu) and KexiDBImageBox. + \return true is the title has been added. */ + static bool updateContextMenuTitleForDataItem(QPopupMenu *menu, KexiDataItemInterface* iface, + const QString& icon = QString::null); + + protected: + KexiDataItemInterface* m_iface; + QGuardedPtr<QPopupMenu> m_contextMenu; + bool m_contextMenuHasTitle; //!< true if KPopupTitle has been added to the context menu. +}; + +class KexiDBAutoField; + +//! An interface allowing to define custom behaviour for subwidget of the KexiDBAutoField +class KexiSubwidgetInterface +{ + public: + KexiSubwidgetInterface(); + virtual ~KexiSubwidgetInterface(); + + virtual bool appendStretchRequired(KexiDBAutoField* autoField) const + { Q_UNUSED(autoField); return false; } + virtual bool subwidgetStretchRequired(KexiDBAutoField* autoField) const + { Q_UNUSED(autoField); return false; } +}; + +#endif diff --git a/kexi/plugins/forms/widgets/kexiframe.cpp b/kexi/plugins/forms/widgets/kexiframe.cpp new file mode 100644 index 00000000..b49386da --- /dev/null +++ b/kexi/plugins/forms/widgets/kexiframe.cpp @@ -0,0 +1,77 @@ +/* This file is part of the KDE project + Copyright (C) 2005-2007 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 "kexiframe.h" + +#include <qpainter.h> +#include <qdrawutil.h> +#include <kexiutils/utils.h> + +//! @internal +class KexiFrame::Private +{ + public: + Private() + { + } + ~Private() + { + } + QColor frameColor; +#if 0 +//todo + KexiFrame::Shape frameShape; + KexiFrame::Shadow frameShadow; +#endif +}; + +//========================================================= + +KexiFrame::KexiFrame( QWidget * parent, const char * name, WFlags f ) + : QFrame(parent, name, f) + , d( new Private() ) +{ + //defaults + d->frameColor = palette().active().foreground(); +//! @todo obtain these defaults from current template's style... + setLineWidth(2); + setFrameStyle(QFrame::StyledPanel|QFrame::Raised); +} + +KexiFrame::~KexiFrame() +{ + delete d; +} + +void KexiFrame::dragMoveEvent( QDragMoveEvent *e ) +{ + QFrame::dragMoveEvent(e); + emit handleDragMoveEvent(e); +} + +void KexiFrame::dropEvent( QDropEvent *e ) +{ + QFrame::dropEvent(e); + emit handleDropEvent(e); +} + +#define ClassName KexiFrame +#define SuperClassName QFrame +#include "kexiframeutils_p.cpp" +#include "kexiframe.moc" diff --git a/kexi/plugins/forms/widgets/kexiframe.h b/kexi/plugins/forms/widgets/kexiframe.h new file mode 100644 index 00000000..8d60d597 --- /dev/null +++ b/kexi/plugins/forms/widgets/kexiframe.h @@ -0,0 +1,84 @@ +/* This file is part of the KDE project + Copyright (C) 2005-2007 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 KexiFrame_H +#define KexiFrame_H + +#include <qframe.h> + +//! @short Frame widget for Kexi forms +class KEXIFORMUTILS_EXPORT KexiFrame : public QFrame +{ + Q_OBJECT +//todo Q_ENUMS( Shape Shadow ) + Q_PROPERTY( QColor frameColor READ frameColor WRITE setFrameColor DESIGNABLE true ) +//todo Q_OVERRIDE( Shape frameShape READ frameShape WRITE setFrameShape ) +//todo Q_OVERRIDE( Shadow frameShadow READ frameShadow WRITE setFrameShadow ) + + public: + KexiFrame( QWidget * parent, const char * name = 0, WFlags f = 0 ); + virtual ~KexiFrame(); + + virtual const QColor& frameColor() const; + +#if 0 +//! @todo more options + enum Shadow { + NoShadow = QFrame::Plain, + Raised = QFrame::Raised, + Sunken = QFrame::Sunken + }; +//! @todo more options + enum Shape { NoFrame = QFrame::NoFrame, //!< no frame + Box = QFrame::Box, //!< rectangular box + Panel = QFrame::Panel, //!< rectangular panel + StyledPanel = QFrame::StyledPanel, //!< rectangular panel depending on the GUI style + GroupBoxPanel = QFrame::GroupBoxPanel //!< rectangular group-box-like panel depending on the GUI style + }; + Shape frameShape() const; + void setFrameShape( KexiFrame::Shape shape ); + Shadow frameShadow() const; + void setFrameShadow( KexiFrame::Shadow shadow ); +#endif + + //! Used to emit handleDragMoveEvent() signal needed to control dragging over the container's surface + virtual void dragMoveEvent( QDragMoveEvent *e ); + + //! Used to emit handleDropEvent() signal needed to control dropping on the container's surface + virtual void dropEvent( QDropEvent *e ); + + public slots: + virtual void setPalette( const QPalette &pal ); + virtual void setFrameColor(const QColor& color); + + signals: + //! Needed to control dragging over the container's surface + void handleDragMoveEvent(QDragMoveEvent *e); + + //! Needed to control dropping on the container's surface + void handleDropEvent(QDropEvent *e); + + protected: + virtual void drawFrame( QPainter * ); + + class Private; + Private *d; +}; + +#endif diff --git a/kexi/plugins/forms/widgets/kexiframeutils_p.cpp b/kexi/plugins/forms/widgets/kexiframeutils_p.cpp new file mode 100644 index 00000000..11b8650a --- /dev/null +++ b/kexi/plugins/forms/widgets/kexiframeutils_p.cpp @@ -0,0 +1,232 @@ +/* This file is part of the KDE project + Copyright (C) 2005-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. +*/ + +/* This file is included by KexiDBLabel and KexiFrame */ + +//! @todo add more frame types +void ClassName::drawFrame( QPainter *p ) +{ + if (frameShape() == QFrame::Box) { + if ( frameShadow() == Plain ) + qDrawPlainRect( p, frameRect(), d->frameColor, lineWidth() ); + else + qDrawShadeRect( p, frameRect(), colorGroup(), frameShadow() == QFrame::Sunken, + lineWidth(), midLineWidth() ); + } + else { + SuperClassName::drawFrame(p); + } +} + +void ClassName::setPalette( const QPalette &pal ) +{ + QPalette pal2(pal); + QColorGroup cg( pal2.active() ); + cg.setColor(QColorGroup::Light, KexiUtils::bleachedColor( d->frameColor, 150 )); + cg.setColor(QColorGroup::Mid, d->frameColor); + cg.setColor(QColorGroup::Dark, d->frameColor.dark(150)); + pal2.setActive(cg); + QColorGroup cg2( pal2.inactive() ); + cg2.setColor(QColorGroup::Light, cg.light() ); + cg2.setColor(QColorGroup::Mid, cg.mid()); + cg2.setColor(QColorGroup::Dark, cg.dark()); + pal2.setInactive(cg2); + SuperClassName::setPalette(pal2); +} + +const QColor& ClassName::frameColor() const +{ + return d->frameColor; +} + +void ClassName::setFrameColor(const QColor& color) +{ + d->frameColor = color; + //update light and dark colors + setPalette( palette() ); +} + +#if 0 +//todo +ClassName::Shape ClassName::frameShape() const +{ + return d->frameShape; +} + +void ClassName::setFrameShape( ClassName::Shape shape ) +{ + d->frameShape = shape; + update(); +} + +ClassName::Shadow ClassName::frameShadow() const +{ + return d->frameShadow; +} + +void ClassName::setFrameShadow( ClassName::Shadow shadow ) +{ + d->frameShadow = shadow; + update(); +} +#endif + +#if 0 +void QFrame::drawFrame( QPainter *p ) +{ + QPoint p1, p2; + QRect r = frameRect(); + int type = fstyle & MShape; + int cstyle = fstyle & MShadow; +#ifdef QT_NO_DRAWUTIL + p->setPen( black ); // #### + p->drawRect( r ); //### a bit too simple +#else + const QColorGroup & g = colorGroup(); + +#ifndef QT_NO_STYLE + QStyleOption opt(lineWidth(),midLineWidth()); + + QStyle::SFlags flags = QStyle::Style_Default; + if (isEnabled()) + flags |= QStyle::Style_Enabled; + if (cstyle == Sunken) + flags |= QStyle::Style_Sunken; + else if (cstyle == Raised) + flags |= QStyle::Style_Raised; + if (hasFocus()) + flags |= QStyle::Style_HasFocus; + if (hasMouse()) + flags |= QStyle::Style_MouseOver; +#endif // QT_NO_STYLE + + switch ( type ) { + + case Box: + if ( cstyle == Plain ) + qDrawPlainRect( p, r, g.foreground(), lwidth ); + else + qDrawShadeRect( p, r, g, cstyle == Sunken, lwidth, + midLineWidth() ); + break; + + case LineEditPanel: + style().drawPrimitive( QStyle::PE_PanelLineEdit, p, r, g, flags, opt ); + break; + + case GroupBoxPanel: + style().drawPrimitive( QStyle::PE_PanelGroupBox, p, r, g, flags, opt ); + break; + + case TabWidgetPanel: + style().drawPrimitive( QStyle::PE_PanelTabWidget, p, r, g, flags, opt ); + break; + + case MenuBarPanel: +#ifndef QT_NO_STYLE + style().drawPrimitive(QStyle::PE_PanelMenuBar, p, r, g, flags, opt); + break; +#endif // fall through to Panel if QT_NO_STYLE + + case ToolBarPanel: +#ifndef QT_NO_STYLE + style().drawPrimitive( QStyle::PE_PanelDockWindow, p, rect(), g, flags, opt); + break; +#endif // fall through to Panel if QT_NO_STYLE + + case StyledPanel: +#ifndef QT_NO_STYLE + if ( cstyle == Plain ) + qDrawPlainRect( p, r, g.foreground(), lwidth ); + else + style().drawPrimitive(QStyle::PE_Panel, p, r, g, flags, opt); + break; +#endif // fall through to Panel if QT_NO_STYLE + + case PopupPanel: +#ifndef QT_NO_STYLE + { + int vextra = style().pixelMetric(QStyle::PM_PopupMenuFrameVerticalExtra, this), + hextra = style().pixelMetric(QStyle::PM_PopupMenuFrameHorizontalExtra, this); + if(vextra > 0 || hextra > 0) { + QRect fr = frameRect(); + int fw = frameWidth(); + if(vextra > 0) { + style().drawControl(QStyle::CE_PopupMenuVerticalExtra, p, this, + QRect(fr.x() + fw, fr.y() + fw, fr.width() - (fw*2), vextra), + g, flags, opt); + style().drawControl(QStyle::CE_PopupMenuVerticalExtra, p, this, + QRect(fr.x() + fw, fr.bottom() - fw - vextra, fr.width() - (fw*2), vextra), + g, flags, opt); + } + if(hextra > 0) { + style().drawControl(QStyle::CE_PopupMenuHorizontalExtra, p, this, + QRect(fr.x() + fw, fr.y() + fw + vextra, hextra, fr.height() - (fw*2) - vextra), + g, flags, opt); + style().drawControl(QStyle::CE_PopupMenuHorizontalExtra, p, this, + QRect(fr.right() - fw - hextra, fr.y() + fw + vextra, hextra, fr.height() - (fw*2) - vextra), + g, flags, opt); + } + } + + if ( cstyle == Plain ) + qDrawPlainRect( p, r, g.foreground(), lwidth ); + else + style().drawPrimitive(QStyle::PE_PanelPopup, p, r, g, flags, opt); + break; + } +#endif // fall through to Panel if QT_NO_STYLE + + case Panel: + if ( cstyle == Plain ) + qDrawPlainRect( p, r, g.foreground(), lwidth ); + else + qDrawShadePanel( p, r, g, cstyle == Sunken, lwidth ); + break; + + case WinPanel: + if ( cstyle == Plain ) + qDrawPlainRect( p, r, g.foreground(), wpwidth ); + else + qDrawWinPanel( p, r, g, cstyle == Sunken ); + break; + case HLine: + case VLine: + if ( type == HLine ) { + p1 = QPoint( r.x(), r.height()/2 ); + p2 = QPoint( r.x()+r.width(), p1.y() ); + } + else { + p1 = QPoint( r.x()+r.width()/2, 0 ); + p2 = QPoint( p1.x(), r.height() ); + } + if ( cstyle == Plain ) { + QPen oldPen = p->pen(); + p->setPen( QPen(g.foreground(),lwidth) ); + p->drawLine( p1, p2 ); + p->setPen( oldPen ); + } + else + qDrawShadeLine( p, p1, p2, g, cstyle == Sunken, + lwidth, midLineWidth() ); + break; + } +#endif // QT_NO_DRAWUTIL + +#endif diff --git a/kexi/plugins/forms/widgets/kexipushbutton.cpp b/kexi/plugins/forms/widgets/kexipushbutton.cpp new file mode 100644 index 00000000..acfda0a4 --- /dev/null +++ b/kexi/plugins/forms/widgets/kexipushbutton.cpp @@ -0,0 +1,32 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr> + Copyright (C) 2004-2005 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 "kexipushbutton.h" + +KexiPushButton::KexiPushButton( const QString & text, QWidget * parent, const char * name ) +: KPushButton(text, parent, name) +{ +} + +KexiPushButton::~KexiPushButton() +{ +} + +#include "kexipushbutton.moc" diff --git a/kexi/plugins/forms/widgets/kexipushbutton.h b/kexi/plugins/forms/widgets/kexipushbutton.h new file mode 100644 index 00000000..12c01631 --- /dev/null +++ b/kexi/plugins/forms/widgets/kexipushbutton.h @@ -0,0 +1,55 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr> + 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 KexiPushButton_H +#define KexiPushButton_H + +#include <kpushbutton.h> +#include "../kexiformeventhandler.h" + +//! @short Push Button widget for Kexi forms +class KEXIFORMUTILS_EXPORT KexiPushButton : public KPushButton +{ + Q_OBJECT + Q_PROPERTY(QString onClickAction READ onClickAction WRITE setOnClickAction DESIGNABLE true) + Q_PROPERTY(QString onClickActionOption READ onClickActionOption WRITE setOnClickActionOption DESIGNABLE true) + + public: + KexiPushButton( const QString & text, QWidget * parent, const char * name = 0 ); + ~KexiPushButton(); + + public slots: + //! action string for "on click" event + //! @see KexiFormPart::slotAssignAction() + //! @see KexiFormEventAction::ActionData + QString onClickAction() const { return m_onClickActionData.string; } + void setOnClickAction(const QString& actionString) { m_onClickActionData.string = actionString; } + + //! action option allowing to select whether the object should be opened in data view mode or printed, etc. + //! @see KexiFormPart::slotAssignAction() + //! @see KexiFormEventAction::ActionData + QString onClickActionOption() const { return m_onClickActionData.option; } + void setOnClickActionOption(const QString& option) { m_onClickActionData.option = option; } + + protected: + KexiFormEventAction::ActionData m_onClickActionData; +}; + +#endif diff --git a/kexi/plugins/importexport/Makefile.am b/kexi/plugins/importexport/Makefile.am new file mode 100644 index 00000000..02d8b733 --- /dev/null +++ b/kexi/plugins/importexport/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = csv diff --git a/kexi/plugins/importexport/csv/Makefile.am b/kexi/plugins/importexport/csv/Makefile.am new file mode 100644 index 00000000..7ad16495 --- /dev/null +++ b/kexi/plugins/importexport/csv/Makefile.am @@ -0,0 +1,21 @@ +include $(top_srcdir)/kexi/Makefile.global + +kde_module_LTLIBRARIES = kexihandler_csv_importexport.la + +kexihandler_csv_importexport_la_SOURCES = kexicsv_importexportpart.cpp kexicsvimportdialog.cpp \ + kexicsvimportoptionsdlg.cpp kexicsvwidgets.cpp kexicsvexport.cpp kexicsvexportwizard.cpp + +kexihandler_csv_importexport_la_LDFLAGS = $(all_libraries) $(KDE_PLUGIN) $(VER_INFO) -module +kexihandler_csv_importexport_la_LIBADD = ../../../core/libkexicore.la \ + ../../../migration/libkeximigrate.la + +INCLUDES= -I$(top_srcdir)/kexi/core -I$(top_srcdir)/kexi \ + -I$(top_srcdir)/kexi/widget -I$(top_srcdir)/kexi/migration \ + -I$(top_srcdir)/kexi/kexiDB $(all_includes) + +METASOURCES = AUTO + +servicesdir=$(kde_servicesdir)/kexi +services_DATA=kexicsv_importexporthandler.desktop + +include ../../Makefile.common diff --git a/kexi/plugins/importexport/csv/kexicsv_importexporthandler.desktop b/kexi/plugins/importexport/csv/kexicsv_importexporthandler.desktop new file mode 100644 index 00000000..1c2a383a --- /dev/null +++ b/kexi/plugins/importexport/csv/kexicsv_importexporthandler.desktop @@ -0,0 +1,51 @@ +[Desktop Entry] +Type=Service +ServiceTypes=Kexi/Handler + +Name=Kexi CSV Data Import/Export Plugin +Name[bg]=Приставка за импортиране/експортиране от CSV в Kexi +Name[ca]=Connector d'importació/Exportació de dades CVS per a Kexi +Name[da]=Kexi CSV data-import/eksport plugin +Name[de]=Kexi CSV-Daten Import-/Export-Modul +Name[el]=Πρόσθετο εισαγωγής/εξαγωγής CSV δεδομένων του Kexi +Name[eo]=Kexi CSV-datuma import-eksport-kromaĵo +Name[es]=Complemento de Kexi para importar y exportar datos CSV +Name[et]=Kexi CSV-andmete impordi/ekspordifilter +Name[fa]=وصلۀ واردات/صادرات دادۀ Kexi CSV +Name[fr]=Module d'importation / exportation de données CSV de Kexi +Name[fy]=Kexi ymport/eksport Plugin foar CSV-gegevens +Name[gl]=Importación de Datos CSV de Kexi +Name[he]=תוסף של Kexi ליבוא/יצוא של מידע מסוג CSV +Name[hu]=Kexi CSV adatimportáló és -exportáló modul +Name[is]=Kexi CSV gagna inn/útflutnings íforrit +Name[it]=Importazione ed esportazione di dati CSV di Kexi +Name[ja]=Kexi CSV データ インポート/エクスポートプラグイン +Name[km]=កម្មវិធីជំនួយក្នុងការនាំចេញ និងនាំចូលទិន្នន័យ CSV សម្រាប់ Kexi +Name[lv]=Kexi CSV datu importa/eksporta spraudnis +Name[nb]=CSV-data import/eksportfilter for Kexi +Name[nds]=CSV-Datenimport-/exportmoduul för Kexi +Name[ne]=केक्सी CSV डेटा आयात/निर्यात प्लगइन +Name[nl]=Kexi import/exportplugin voor CSV-gegevens +Name[pl]=Wtyczka importu/eksportu danych CSV dla Kexi +Name[pt]=Importação de Dados CSV do Kexi +Name[pt_BR]=Plugin de Importação/Exportação de Dados CSV do Kexi +Name[ru]=Модуль импорта/экспорта CSV (значения через запятую) для Kexi +Name[se]=Kexi CSV-dáhta sisa-/olggosfievrridan lassemoduvla +Name[sk]=Modul Kexi pre import a export CSV dát +Name[sl]=Vstavek za uvoz/izvoz podatkov CVS za Kexi +Name[sr]=Kexi-јев прикључак за увоз и извоз из CSV-а +Name[sr@Latn]=Kexi-jev priključak za uvoz i izvoz iz CSV-a +Name[sv]=Kexi insticksprogram för import/export av CSV-data +Name[uk]=Втулок імпорту/експорту CSV-даних для Kexi +Name[uz]=Kexi CSV maʼlumot import/eksport plagini +Name[uz@cyrillic]=Kexi CSV маълумот импорт/экспорт плагини +Name[zh_CN]=Kexi CSV 数据导入/导出插件 +Name[zh_TW]=Kexi CSV 資料匯入/匯出外掛程式 + +X-KDE-Library=kexihandler_csv_importexport +X-KDE-ParentApp=kexi +X-Kexi-PartVersion=2 +X-Kexi-TypeName=csv_importexport +X-Kexi-GroupIcon=csv_importexport +X-Kexi-ItemIcon=csv_importexport +X-Kexi-NoObject=true diff --git a/kexi/plugins/importexport/csv/kexicsv_importexportpart.cpp b/kexi/plugins/importexport/csv/kexicsv_importexportpart.cpp new file mode 100644 index 00000000..caa8640d --- /dev/null +++ b/kexi/plugins/importexport/csv/kexicsv_importexportpart.cpp @@ -0,0 +1,87 @@ +/* This file is part of the KDE project + Copyright (C) 2005 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 "kexicsv_importexportpart.h" +#include "kexicsvimportdialog.h" +#include "kexicsvexportwizard.h" +#include <core/keximainwindow.h> +#include <core/kexiproject.h> +#include <kexiutils/utils.h> + +#include <kgenericfactory.h> + +KexiCSVImportExportPart::KexiCSVImportExportPart(QObject *parent, const char *name, const QStringList &args) + : KexiInternalPart(parent, name, args) +{ +} + +KexiCSVImportExportPart::~KexiCSVImportExportPart() +{ +} + +QWidget *KexiCSVImportExportPart::createWidget(const char* widgetClass, KexiMainWindow* mainWin, + QWidget *parent, const char *objName, QMap<QString,QString>* args ) +{ + if (0==qstrcmp(widgetClass, "KexiCSVImportDialog")) { + KexiCSVImportDialog::Mode mode = (args && (*args)["sourceType"]=="file") + ? KexiCSVImportDialog::File : KexiCSVImportDialog::Clipboard; + KexiCSVImportDialog *dlg = new KexiCSVImportDialog( mode, mainWin, parent, objName ); + m_cancelled = dlg->cancelled(); + if (m_cancelled) { + delete dlg; + return 0; + } + return dlg; + } + else if (0==qstrcmp(widgetClass, "KexiCSVExportWizard")) { + if (!args) + return 0; + KexiCSVExport::Options options; + if (!options.assign( *args )) + return 0; + KexiCSVExportWizard *dlg = new KexiCSVExportWizard( options, mainWin, parent, objName); + m_cancelled = dlg->cancelled(); + if (m_cancelled) { + delete dlg; + return 0; + } + return dlg; + } + return 0; +} + +bool KexiCSVImportExportPart::executeCommand(KexiMainWindow* mainWin, const char* commandName, + QMap<QString,QString>* args) +{ + if (0==qstrcmp(commandName, "KexiCSVExport")) { + KexiCSVExport::Options options; + if (!options.assign( *args )) + return false; + KexiDB::TableOrQuerySchema tableOrQuery( + mainWin->project()->dbConnection(), options.itemId); + QTextStream *stream = 0; + if (args->contains("textStream")) + stream = KexiUtils::stringToPtr<QTextStream>( (*args)["textStream"] ); + return KexiCSVExport::exportData(tableOrQuery, options, -1, stream); + } + return false; +} + +K_EXPORT_COMPONENT_FACTORY( kexihandler_csv_importexport, + KGenericFactory<KexiCSVImportExportPart>("kexihandler_csv_importexport") ) diff --git a/kexi/plugins/importexport/csv/kexicsv_importexportpart.h b/kexi/plugins/importexport/csv/kexicsv_importexportpart.h new file mode 100644 index 00000000..8ee8e8cd --- /dev/null +++ b/kexi/plugins/importexport/csv/kexicsv_importexportpart.h @@ -0,0 +1,44 @@ +/* This file is part of the KDE project + Copyright (C) 2005 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 KEXI_MIGRATION_PART_H +#define KEXI_MIGRATION_PART_H + +#include <core/kexiinternalpart.h> +#include "kexicsvexportwizard.h" + +/*! Internal part for CSV data import/export dialogs. */ +class KexiCSVImportExportPart : public KexiInternalPart +{ + public: + KexiCSVImportExportPart(QObject *parent, const char *name, const QStringList &args); + virtual ~KexiCSVImportExportPart(); + + /*! Reimplemented to return wizard object. */ + virtual QWidget *createWidget(const char* widgetClass, KexiMainWindow* mainWin, + QWidget *parent, const char *objName = 0, QMap<QString,QString>* args = 0); + + /*! Reimplemented to execute a command \a commandName (nonvisual). The result are put into the \a args. */ + virtual bool executeCommand(KexiMainWindow* mainWin, const char* commandName, + QMap<QString,QString>* args = 0); + + protected: +}; + +#endif diff --git a/kexi/plugins/importexport/csv/kexicsvexport.cpp b/kexi/plugins/importexport/csv/kexicsvexport.cpp new file mode 100644 index 00000000..b83f85a7 --- /dev/null +++ b/kexi/plugins/importexport/csv/kexicsvexport.cpp @@ -0,0 +1,271 @@ +/* This file is part of the KDE project + Copyright (C) 2005,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 "kexicsvexport.h" +#include "kexicsvwidgets.h" +#include <main/startup/KexiStartupFileDialog.h> +#include <kexidb/cursor.h> +#include <kexidb/utils.h> +#include <core/keximainwindow.h> +#include <core/kexiproject.h> +#include <core/kexipartinfo.h> +#include <core/kexipartmanager.h> +#include <core/kexiguimsghandler.h> +#include <kexiutils/utils.h> +#include <widget/kexicharencodingcombobox.h> + +#include <qcheckbox.h> +#include <qgroupbox.h> +#include <qclipboard.h> +#include <kapplication.h> +#include <klocale.h> +#include <kiconloader.h> +#include <kactivelabel.h> +#include <kpushbutton.h> +#include <kapplication.h> +#include <kdebug.h> +#include <ksavefile.h> + + +using namespace KexiCSVExport; + +Options::Options() + : mode(File), itemId(0), addColumnNames(true) +{ +} + +bool Options::assign( QMap<QString,QString>& args ) +{ + mode = (args["destinationType"]=="file") + ? KexiCSVExport::File : KexiCSVExport::Clipboard; + + if (args.contains("delimiter")) + delimiter = args["delimiter"]; + else + delimiter = (mode==File) ? KEXICSV_DEFAULT_FILE_DELIMITER : KEXICSV_DEFAULT_CLIPBOARD_DELIMITER; + + if (args.contains("textQuote")) + textQuote = args["textQuote"]; + else + textQuote = (mode==File) ? KEXICSV_DEFAULT_FILE_TEXT_QUOTE : KEXICSV_DEFAULT_CLIPBOARD_TEXT_QUOTE; + + bool ok; + itemId = args["itemId"].toInt(&ok); + if (!ok || itemId<=0) + return false; + if (args.contains("forceDelimiter")) + forceDelimiter = args["forceDelimiter"]; + if (args.contains("addColumnNames")) + addColumnNames = (args["addColumnNames"]=="1"); + return true; +} + +//------------------------------------ + +bool KexiCSVExport::exportData(KexiDB::TableOrQuerySchema& tableOrQuery, + const Options& options, int rowCount, QTextStream *predefinedTextStream) +{ + KexiDB::Connection* conn = tableOrQuery.connection(); + if (!conn) + return false; + + if (rowCount == -1) + rowCount = KexiDB::rowCount(tableOrQuery); + if (rowCount == -1) + return false; + +//! @todo move this to non-GUI location so it can be also used via command line +//! @todo add a "finish" page with a progressbar. +//! @todo look at rowCount whether the data is really large; +//! if so: avoid copying to clipboard (or ask user) because of system memory + +//! @todo OPTIMIZATION: use fieldsExpanded(true /*UNIQUE*/) +//! @todo OPTIMIZATION? (avoid multiple data retrieving) look for already fetched data within KexiProject.. + + KexiDB::QuerySchema* query = tableOrQuery.query(); + if (!query) + query = tableOrQuery.table()->query(); + + KexiDB::QueryColumnInfo::Vector fields( query->fieldsExpanded( KexiDB::QuerySchema::WithInternalFields ) ); + QString buffer; + + KSaveFile *kSaveFile = 0; + QTextStream *stream = 0; + + const bool copyToClipboard = options.mode==Clipboard; + if (copyToClipboard) { +//! @todo (during exporting): enlarge bufSize by factor of 2 when it became too small + uint bufSize = QMIN((rowCount<0 ? 10 : rowCount) * fields.count() * 20, 128000); + buffer.reserve( bufSize ); + if (buffer.capacity() < bufSize) { + kdWarning() << "KexiCSVExportWizard::exportData() cannot allocate memory for " << bufSize + << " characters" << endl; + return false; + } + } + else { + if (predefinedTextStream) { + stream = predefinedTextStream; + } + else { + if (options.fileName.isEmpty()) {//sanity + kdWarning() << "KexiCSVExportWizard::exportData(): fname is empty" << endl; + return false; + } + kSaveFile = new KSaveFile(options.fileName); + if (0 == kSaveFile->status()) + stream = kSaveFile->textStream(); + if (0 != kSaveFile->status() || !stream) {//sanity + kdWarning() << "KexiCSVExportWizard::exportData(): status != 0 or stream == 0" << endl; + delete kSaveFile; + return false; + } + } + } + +//! @todo escape strings + +#define _ERR \ + delete [] isText; \ + if (kSaveFile) { kSaveFile->abort(); delete kSaveFile; } \ + return false + +#define APPEND(what) \ + if (copyToClipboard) buffer.append(what); else (*stream) << (what) + +// line endings should be as in RFC 4180 +#define CSV_EOLN "\r\n" + + // 0. Cache information + const uint fieldsCount = query->fieldsExpanded().count(); //real fields count without internals + const QCString delimiter( options.delimiter.left(1).latin1() ); + const bool hasTextQuote = !options.textQuote.isEmpty(); + const QString textQuote( options.textQuote.left(1) ); + const QCString escapedTextQuote( (textQuote + textQuote).latin1() ); //ok? + //cache for faster checks + bool *isText = new bool[fieldsCount]; + bool *isDateTime = new bool[fieldsCount]; + bool *isTime = new bool[fieldsCount]; + bool *isBLOB = new bool[fieldsCount]; + uint *visibleFieldIndex = new uint[fieldsCount]; +// bool isInteger[fieldsCount]; //cache for faster checks +// bool isFloatingPoint[fieldsCount]; //cache for faster checks + for (uint i=0; i<fieldsCount; i++) { + KexiDB::QueryColumnInfo* ci; + const int indexForVisibleLookupValue = fields[i]->indexForVisibleLookupValue(); + if (-1 != indexForVisibleLookupValue) { + ci = query->expandedOrInternalField( indexForVisibleLookupValue ); + visibleFieldIndex[i] = indexForVisibleLookupValue; + } + else { + ci = fields[i]; + visibleFieldIndex[i] = i; + } + + isText[i] = ci->field->isTextType(); + isDateTime[i] = ci->field->type()==KexiDB::Field::DateTime; + isTime[i] = ci->field->type()==KexiDB::Field::Time; + isBLOB[i] = ci->field->type()==KexiDB::Field::BLOB; +// isInteger[i] = fields[i]->field->isIntegerType() +// || fields[i]->field->type()==KexiDB::Field::Boolean; +// isFloatingPoint[i] = fields[i]->field->isFPNumericType(); + } + + // 1. Output column names + if (options.addColumnNames) { + for (uint i=0; i<fieldsCount; i++) { + if (i>0) + APPEND( delimiter ); + if (hasTextQuote){ + APPEND( textQuote + fields[i]->captionOrAliasOrName().replace(textQuote, escapedTextQuote) + textQuote ); + } + else { + APPEND( fields[i]->captionOrAliasOrName() ); + } + } + APPEND(CSV_EOLN); + } + + KexiGUIMessageHandler handler; + KexiDB::Cursor *cursor = conn->executeQuery(*query); + if (!cursor) { + handler.showErrorMessage(conn); + _ERR; + } + for (cursor->moveFirst(); !cursor->eof() && !cursor->error(); cursor->moveNext()) { + const uint realFieldCount = QMIN(cursor->fieldCount(), fieldsCount); + for (uint i=0; i<realFieldCount; i++) { + const uint real_i = visibleFieldIndex[i]; + if (i>0) + APPEND( delimiter ); + if (cursor->value(real_i).isNull()) + continue; + if (isText[real_i]) { + if (hasTextQuote) + APPEND( textQuote + QString(cursor->value(real_i).toString()).replace(textQuote, escapedTextQuote) + textQuote ); + else + APPEND( cursor->value(real_i).toString() ); + } + else if (isDateTime[real_i]) { //avoid "T" in ISO DateTime + APPEND( cursor->value(real_i).toDateTime().date().toString(Qt::ISODate)+" " + + cursor->value(real_i).toDateTime().time().toString(Qt::ISODate) ); + } + else if (isTime[real_i]) { //time is temporarily stored as null date + time... + APPEND( cursor->value(real_i).toTime().toString(Qt::ISODate) ); + } + else if (isBLOB[real_i]) { //BLOB is escaped in a special way + if (hasTextQuote) +//! @todo add options to suppport other types from KexiDB::BLOBEscapingType enum... + APPEND( textQuote + KexiDB::escapeBLOB(cursor->value(real_i).toByteArray(), KexiDB::BLOBEscapeHex) + textQuote ); + else + APPEND( KexiDB::escapeBLOB(cursor->value(real_i).toByteArray(), KexiDB::BLOBEscapeHex) ); + } + else {//other types + APPEND( cursor->value(real_i).toString() ); + } + } + APPEND(CSV_EOLN); + } + + if (copyToClipboard) + buffer.squeeze(); + + if (!conn->deleteCursor(cursor)) { + handler.showErrorMessage(conn); + _ERR; + } + + if (copyToClipboard) + kapp->clipboard()->setText(buffer, QClipboard::Clipboard); + + delete [] isText; + delete [] isDateTime; + delete [] isTime; + delete [] isBLOB; + delete [] visibleFieldIndex; + + if (kSaveFile) { + if (!kSaveFile->close()) { + kdWarning() << "KexiCSVExportWizard::exportData(): error close(); status == " + << kSaveFile->status() << endl; + } + delete kSaveFile; + } + return true; +} diff --git a/kexi/plugins/importexport/csv/kexicsvexport.h b/kexi/plugins/importexport/csv/kexicsvexport.h new file mode 100644 index 00000000..7c8138fe --- /dev/null +++ b/kexi/plugins/importexport/csv/kexicsvexport.h @@ -0,0 +1,58 @@ +/* This file is part of the KDE project + Copyright (C) 2005,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 KEXI_CSVEXPORT_H +#define KEXI_CSVEXPORT_H + +#include <kexidb/utils.h> + +namespace KexiCSVExport +{ + +//! Exporting mode: a file or clipboard +enum Mode { Clipboard, File }; + +//! Options used in KexiCSVExportWizard contructor. +class Options { + public: + Options(); + + //! Assigns \a args. \return false on failure. + bool assign( QMap<QString,QString>& args ); + + Mode mode; + int itemId; //!< Table or query ID + QString fileName; + QString delimiter; + QString forceDelimiter; //!< Used for "clipboard" mode + QString textQuote; + bool addColumnNames : 1; +}; + +/*! Exports data. \return false on failure. + @param options options for the export + @param rowCount row count of the input data or -1 if the row cound has not yet been computed + @param predefinedTextStream text stream that should be used instead of writing to a file +*/ +bool exportData(KexiDB::TableOrQuerySchema& tableOrQuery, const Options& options, int rowCount = -1, + QTextStream *predefinedTextStream = 0); + +} + +#endif diff --git a/kexi/plugins/importexport/csv/kexicsvexportwizard.cpp b/kexi/plugins/importexport/csv/kexicsvexportwizard.cpp new file mode 100644 index 00000000..11c0cff0 --- /dev/null +++ b/kexi/plugins/importexport/csv/kexicsvexportwizard.cpp @@ -0,0 +1,431 @@ +/* This file is part of the KDE project + Copyright (C) 2005,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 "kexicsvexportwizard.h" +#include "kexicsvwidgets.h" +#include <main/startup/KexiStartupFileDialog.h> +#include <kexidb/cursor.h> +#include <kexidb/utils.h> +#include <core/keximainwindow.h> +#include <core/kexiproject.h> +#include <core/kexipartinfo.h> +#include <core/kexipartmanager.h> +#include <core/kexiguimsghandler.h> +#include <kexiutils/utils.h> +#include <widget/kexicharencodingcombobox.h> + +#include <qcheckbox.h> +#include <qgroupbox.h> +#include <qclipboard.h> +#include <kapplication.h> +#include <klocale.h> +#include <kiconloader.h> +#include <kactivelabel.h> +#include <kpushbutton.h> +#include <kapplication.h> +#include <kdebug.h> +#include <ksavefile.h> + + +KexiCSVExportWizard::KexiCSVExportWizard( const KexiCSVExport::Options& options, + KexiMainWindow* mainWin, QWidget * parent, const char * name ) + : KWizard(parent, name) + , m_options(options) +// , m_mode(mode) +// , m_itemId(itemId) + , m_mainWin(mainWin) + , m_fileSavePage(0) + , m_defaultsBtn(0) + , m_rowCount(-1) + , m_rowCountDetermined(false) + , m_cancelled(false) +{ + if (m_options.mode==KexiCSVExport::Clipboard) { + finishButton()->setText(i18n("Copy")); + backButton()->hide(); + } + else { + finishButton()->setText(i18n("Export")); + } + helpButton()->hide(); + + QString infoLblFromText; + KexiGUIMessageHandler msgh(this); + m_tableOrQuery = new KexiDB::TableOrQuerySchema( + m_mainWin->project()->dbConnection(), m_options.itemId); + if (m_tableOrQuery->table()) { + if (m_options.mode==KexiCSVExport::Clipboard) { + setCaption(i18n("Copy Data From Table to Clipboard")); + infoLblFromText = i18n("Copying data from table:"); + } + else { + setCaption(i18n("Export Data From Table to CSV File")); + infoLblFromText = i18n("Exporting data from table:"); + } + } + else if (m_tableOrQuery->query()) { + if (m_options.mode==KexiCSVExport::Clipboard) { + setCaption(i18n("Copy Data From Query to Clipboard")); + infoLblFromText = i18n("Copying data from table:"); + } + else { + setCaption(i18n("Export Data From Query to CSV File")); + infoLblFromText = i18n("Exporting data from query:"); + } + } + else { + msgh.showErrorMessage(m_mainWin->project()->dbConnection(), + i18n("Could not open data for exporting.")); + m_cancelled = true; + return; + } + // OK, source data found. + + // Setup pages + + // 1. File Save Page + if (m_options.mode==KexiCSVExport::File) { + m_fileSavePage = new KexiStartupFileDialog( + ":CSVImportExport", //startDir + KexiStartupFileDialog::Custom | KexiStartupFileDialog::SavingFileBasedDB, + this, "m_fileSavePage"); + m_fileSavePage->setMinimumHeight(kapp->desktop()->height()/2); + m_fileSavePage->setAdditionalFilters( csvMimeTypes() ); + m_fileSavePage->setDefaultExtension("csv"); + m_fileSavePage->setLocationText( KexiUtils::stringToFileName(m_tableOrQuery->captionOrName()) ); + connect(m_fileSavePage, SIGNAL(rejected()), this, SLOT(reject())); + addPage(m_fileSavePage, i18n("Enter Name of File You Want to Save Data To")); + } + + // 2. Export options + m_exportOptionsPage = new QWidget(this, "m_exportOptionsPage"); + QGridLayout *exportOptionsLyr = new QGridLayout( m_exportOptionsPage, 6, 3, + KDialogBase::marginHint(), KDialogBase::spacingHint(), "exportOptionsLyr"); + m_infoLblFrom = new KexiCSVInfoLabel( infoLblFromText, m_exportOptionsPage ); + KexiPart::Info *partInfo = Kexi::partManager().infoForMimeType( + m_tableOrQuery->table() ? "kexi/table" : "kexi/query"); + if (partInfo) + m_infoLblFrom->setIcon(partInfo->itemIcon()); + m_infoLblFrom->separator()->hide(); + exportOptionsLyr->addMultiCellWidget(m_infoLblFrom, 0, 0, 0, 2); + + m_infoLblTo = new KexiCSVInfoLabel( + (m_options.mode==KexiCSVExport::File) ? i18n("To CSV file:") : i18n("To clipboard:"), + m_exportOptionsPage + ); + if (m_options.mode==KexiCSVExport::Clipboard) + m_infoLblTo->setIcon("editpaste"); + exportOptionsLyr->addMultiCellWidget(m_infoLblTo, 1, 1, 0, 2); + + m_showOptionsButton = new KPushButton(KGuiItem(i18n("Show Options >>"), "configure"), + m_exportOptionsPage); + connect(m_showOptionsButton, SIGNAL(clicked()), this, SLOT(slotShowOptionsButtonClicked())); + exportOptionsLyr->addMultiCellWidget(m_showOptionsButton, 2, 2, 0, 0); + m_showOptionsButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + + // -<options section> + m_exportOptionsSection = new QGroupBox(1, Vertical, i18n("Options"), m_exportOptionsPage, + "m_exportOptionsSection"); + m_exportOptionsSection->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + exportOptionsLyr->addMultiCellWidget(m_exportOptionsSection, 3, 3, 0, 1); + QWidget *exportOptionsSectionWidget + = new QWidget(m_exportOptionsSection, "exportOptionsSectionWidget"); + QGridLayout *exportOptionsSectionLyr = new QGridLayout( exportOptionsSectionWidget, 5, 2, + 0, KDialogBase::spacingHint(), "exportOptionsLyr"); + + // -delimiter + m_delimiterWidget = new KexiCSVDelimiterWidget(false, //!lineEditOnBottom + exportOptionsSectionWidget); + m_delimiterWidget->setDelimiter(defaultDelimiter()); + exportOptionsSectionLyr->addWidget( m_delimiterWidget, 0, 1 ); + QLabel *delimiterLabel = new QLabel(m_delimiterWidget, i18n("Delimiter:"), exportOptionsSectionWidget); + exportOptionsSectionLyr->addWidget( delimiterLabel, 0, 0 ); + + // -text quote + QWidget *textQuoteWidget = new QWidget(exportOptionsSectionWidget); + QHBoxLayout *textQuoteLyr = new QHBoxLayout(textQuoteWidget); + exportOptionsSectionLyr->addWidget(textQuoteWidget, 1, 1); + m_textQuote = new KexiCSVTextQuoteComboBox( textQuoteWidget ); + m_textQuote->setTextQuote(defaultTextQuote()); + textQuoteLyr->addWidget( m_textQuote ); + textQuoteLyr->addStretch(0); + QLabel *textQuoteLabel = new QLabel(m_textQuote, i18n("Text quote:"), exportOptionsSectionWidget); + exportOptionsSectionLyr->addWidget( textQuoteLabel, 1, 0 ); + + // - character encoding + m_characterEncodingCombo = new KexiCharacterEncodingComboBox( exportOptionsSectionWidget ); + m_characterEncodingCombo->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + exportOptionsSectionLyr->addWidget( m_characterEncodingCombo, 2, 1 ); + QLabel *characterEncodingLabel = new QLabel(m_characterEncodingCombo, i18n("Text encoding:"), + exportOptionsSectionWidget); + exportOptionsSectionLyr->addWidget( characterEncodingLabel, 2, 0 ); + + // - checkboxes + m_addColumnNamesCheckBox = new QCheckBox(i18n("Add column names as the first row"), + exportOptionsSectionWidget); + m_addColumnNamesCheckBox->setChecked(true); + exportOptionsSectionLyr->addWidget( m_addColumnNamesCheckBox, 3, 1 ); +//! @todo 1.1: for copying use "Always use above options for copying" string + m_alwaysUseCheckBox = new QCheckBox(i18n("Always use above options for exporting"), + m_exportOptionsPage); + exportOptionsLyr->addMultiCellWidget(m_alwaysUseCheckBox, 4, 4, 0, 1); +// exportOptionsSectionLyr->addWidget( m_alwaysUseCheckBox, 4, 1 ); + m_exportOptionsSection->hide(); + m_alwaysUseCheckBox->hide(); + // -</options section> + +// exportOptionsLyr->setColStretch(3, 1); + exportOptionsLyr->addMultiCell( + new QSpacerItem( 0, 0, QSizePolicy::Preferred, QSizePolicy::MinimumExpanding), 5, 5, 0, 1 ); + +// addPage(m_exportOptionsPage, i18n("Set Export Options")); + addPage(m_exportOptionsPage, m_options.mode==KexiCSVExport::Clipboard ? i18n("Copying") : i18n("Exporting")); + setFinishEnabled(m_exportOptionsPage, true); + + // load settings + kapp->config()->setGroup("ImportExport"); + if (m_options.mode!=KexiCSVExport::Clipboard && readBoolEntry("ShowOptionsInCSVExportDialog", false)) { + show(); + slotShowOptionsButtonClicked(); + } + if (readBoolEntry("StoreOptionsForCSVExportDialog", false)) { + // load defaults: + m_alwaysUseCheckBox->setChecked(true); + QString s = readEntry("DefaultDelimiterForExportingCSVFiles", defaultDelimiter()); + if (!s.isEmpty()) + m_delimiterWidget->setDelimiter(s); + s = readEntry("DefaultTextQuoteForExportingCSVFiles", defaultTextQuote()); + m_textQuote->setTextQuote(s); //will be invaliudated here, so not a problem + s = readEntry("DefaultEncodingForExportingCSVFiles"); + if (!s.isEmpty()) + m_characterEncodingCombo->setSelectedEncoding(s); + m_addColumnNamesCheckBox->setChecked( + readBoolEntry("AddColumnNamesForExportingCSVFiles", true) ); + } + + updateGeometry(); + + // -keep widths equal on page #2: + int width = QMAX( m_infoLblFrom->leftLabel()->sizeHint().width(), + m_infoLblTo->leftLabel()->sizeHint().width()); + m_infoLblFrom->leftLabel()->setFixedWidth(width); + m_infoLblTo->leftLabel()->setFixedWidth(width); +} + +KexiCSVExportWizard::~KexiCSVExportWizard() +{ + delete m_tableOrQuery; +} + +bool KexiCSVExportWizard::cancelled() const +{ + return m_cancelled; +} + +void KexiCSVExportWizard::showPage ( QWidget * page ) +{ + if (page == m_fileSavePage) { + m_fileSavePage->setFocus(); + } + else if (page==m_exportOptionsPage) { + if (m_options.mode==KexiCSVExport::File) + m_infoLblTo->setFileName( m_fileSavePage->currentFileName() ); + QString text = m_tableOrQuery->captionOrName(); + if (!m_rowCountDetermined) { + //do this costly operation only once + m_rowCount = KexiDB::rowCount(*m_tableOrQuery); + m_rowCountDetermined = true; + } + int columns = KexiDB::fieldCount(*m_tableOrQuery); + text += "\n"; + if (m_rowCount>0) + text += i18n("(rows: %1, columns: %2)").arg(m_rowCount).arg(columns); + else + text += i18n("(columns: %1)").arg(columns); + m_infoLblFrom->setLabelText(text); + QFontMetrics fm(m_infoLblFrom->fileNameLabel()->font()); + m_infoLblFrom->fileNameLabel()->setFixedHeight( fm.height() * 2 + fm.lineSpacing() ); + if (m_defaultsBtn) + m_defaultsBtn->show(); + } + + if (page!=m_exportOptionsPage) { + if (m_defaultsBtn) + m_defaultsBtn->hide(); + } + + KWizard::showPage(page); +} + +void KexiCSVExportWizard::next() +{ + if (currentPage() == m_fileSavePage) { + if (!m_fileSavePage->checkFileName()) + return; + KWizard::next(); + finishButton()->setFocus(); + return; + } + KWizard::next(); +} + +void KexiCSVExportWizard::done(int result) +{ + if (QDialog::Accepted == result) { + if (m_fileSavePage) + m_options.fileName = m_fileSavePage->currentFileName(); + m_options.delimiter = m_delimiterWidget->delimiter(); + m_options.textQuote = m_textQuote->textQuote(); + m_options.addColumnNames = m_addColumnNamesCheckBox->isChecked(); + if (!KexiCSVExport::exportData(*m_tableOrQuery, m_options)) + return; + } + else if (QDialog::Rejected == result) { + //nothing to do + } + + //store options + kapp->config()->setGroup("ImportExport"); + if (m_options.mode!=KexiCSVExport::Clipboard) + writeEntry("ShowOptionsInCSVExportDialog", m_exportOptionsSection->isVisible()); + const bool store = m_alwaysUseCheckBox->isChecked(); + writeEntry("StoreOptionsForCSVExportDialog", store); + // only save if an option differs from default + + if (store && m_delimiterWidget->delimiter()!=defaultDelimiter()) + writeEntry("DefaultDelimiterForExportingCSVFiles", m_delimiterWidget->delimiter()); + else + deleteEntry("DefaultDelimiterForExportingCSVFiles"); + if (store && m_textQuote->textQuote()!=defaultTextQuote()) + writeEntry("DefaultTextQuoteForExportingCSVFiles", m_textQuote->textQuote()); + else + deleteEntry("DefaultTextQuoteForExportingCSVFiles"); + if (store && !m_characterEncodingCombo->defaultEncodingSelected()) + writeEntry("DefaultEncodingForExportingCSVFiles", m_characterEncodingCombo->selectedEncoding()); + else + deleteEntry("DefaultEncodingForExportingCSVFiles"); + if (store && !m_addColumnNamesCheckBox->isChecked()) + writeEntry("AddColumnNamesForExportingCSVFiles", m_addColumnNamesCheckBox->isChecked()); + else + deleteEntry("AddColumnNamesForExportingCSVFiles"); + + KWizard::done(result); +} + +void KexiCSVExportWizard::slotShowOptionsButtonClicked() +{ + if (m_exportOptionsSection->isVisible()) { + m_showOptionsButton->setText(i18n("Show Options >>")); + m_exportOptionsSection->hide(); + m_alwaysUseCheckBox->hide(); + if (m_defaultsBtn) + m_defaultsBtn->hide(); + } + else { + m_showOptionsButton->setText(i18n("Hide Options <<")); + m_exportOptionsSection->show(); + m_alwaysUseCheckBox->show(); + if (m_defaultsBtn) + m_defaultsBtn->show(); + } +} + +void KexiCSVExportWizard::layOutButtonRow( QHBoxLayout * layout ) +{ + QWizard::layOutButtonRow( layout ); + + //find the last sublayout + QLayout *l = 0; + for (QLayoutIterator lit( layout->iterator() ); lit.current(); ++lit) + l = lit.current()->layout(); + if (dynamic_cast<QBoxLayout*>(l)) { + if (!m_defaultsBtn) { + m_defaultsBtn = new KPushButton(i18n("Defaults"), this); + QWidget::setTabOrder(backButton(), m_defaultsBtn); + connect(m_defaultsBtn, SIGNAL(clicked()), this, SLOT(slotDefaultsButtonClicked())); + } + if (!m_exportOptionsSection->isVisible()) + m_defaultsBtn->hide(); + dynamic_cast<QBoxLayout*>(l)->insertWidget(0, m_defaultsBtn); + } +} + +void KexiCSVExportWizard::slotDefaultsButtonClicked() +{ + m_delimiterWidget->setDelimiter(defaultDelimiter()); + m_textQuote->setTextQuote(defaultTextQuote()); + m_addColumnNamesCheckBox->setChecked(true); + m_characterEncodingCombo->selectDefaultEncoding(); +} + +static QString convertKey(const char *key, KexiCSVExport::Mode mode) +{ + QString _key(QString::fromLatin1(key)); + if (mode == KexiCSVExport::Clipboard) { + _key.replace("Exporting", "Copying"); + _key.replace("Export", "Copy"); + _key.replace("CSVFiles", "CSVToClipboard"); + } + return _key; +} + +bool KexiCSVExportWizard::readBoolEntry(const char *key, bool defaultValue) +{ + return kapp->config()->readBoolEntry(convertKey(key, m_options.mode), defaultValue); +} + +QString KexiCSVExportWizard::readEntry(const char *key, const QString& defaultValue) +{ + return kapp->config()->readEntry(convertKey(key, m_options.mode), defaultValue); +} + +void KexiCSVExportWizard::writeEntry(const char *key, const QString& value) +{ + kapp->config()->writeEntry(convertKey(key, m_options.mode), value); +} + +void KexiCSVExportWizard::writeEntry(const char *key, bool value) +{ + kapp->config()->writeEntry(convertKey(key, m_options.mode), value); +} + +void KexiCSVExportWizard::deleteEntry(const char *key) +{ + kapp->config()->deleteEntry(convertKey(key, m_options.mode)); +} + +QString KexiCSVExportWizard::defaultDelimiter() const +{ + if (m_options.mode==KexiCSVExport::Clipboard) { + if (!m_options.forceDelimiter.isEmpty()) + return m_options.forceDelimiter; + else + return KEXICSV_DEFAULT_CLIPBOARD_DELIMITER; + } + return KEXICSV_DEFAULT_FILE_DELIMITER; +} + +QString KexiCSVExportWizard::defaultTextQuote() const +{ + if (m_options.mode==KexiCSVExport::Clipboard) + return KEXICSV_DEFAULT_CLIPBOARD_TEXT_QUOTE; + return KEXICSV_DEFAULT_FILE_TEXT_QUOTE; +} + +#include "kexicsvexportwizard.moc" diff --git a/kexi/plugins/importexport/csv/kexicsvexportwizard.h b/kexi/plugins/importexport/csv/kexicsvexportwizard.h new file mode 100644 index 00000000..8fdaecd0 --- /dev/null +++ b/kexi/plugins/importexport/csv/kexicsvexportwizard.h @@ -0,0 +1,113 @@ +/* This file is part of the KDE project + Copyright (C) 2005,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 KEXI_CSVEXPORTWIZARD_H +#define KEXI_CSVEXPORTWIZARD_H + +#include <kwizard.h> + +#include "kexicsvexport.h" + +class QCheckBox; +class QGroupBox; +class KPushButton; +class KexiMainWindow; +class KexiStartupFileDialog; +class KexiCSVDelimiterWidget; +class KexiCSVTextQuoteComboBox; +class KexiCSVInfoLabel; +class KexiCharacterEncodingComboBox; +namespace KexiDB { + class TableOrQuerySchema; +} + +/*! @short Kexi CSV export wizard + Supports exporting to a file and to a clipboard. */ +class KexiCSVExportWizard : public KWizard +{ + Q_OBJECT + + public: + KexiCSVExportWizard( const KexiCSVExport::Options& options, KexiMainWindow* mainWin, + QWidget * parent = 0, const char * name = 0 ); + + virtual ~KexiCSVExportWizard(); + + bool cancelled() const; + + virtual void showPage ( QWidget * page ); + + protected slots: + virtual void next(); + virtual void done(int result); + void slotShowOptionsButtonClicked(); + void slotDefaultsButtonClicked(); + + protected: + //! reimplemented to add "Defaults" button on the left hand + virtual void layOutButtonRow( QHBoxLayout * layout ); + + //! \return default delimiter depending on mode. + QString defaultDelimiter() const; + + //! \return default text quote depending on mode. + QString defaultTextQuote() const; + + //! Helper, works like kapp->config()->readBoolEntry(const char*, bool) but if mode is Clipboard, + //! "Exporting" is replaced with "Copying" and "Export" is replaced with "Copy" + //! and "CSVFiles" is replaced with "CSVToClipboard" + //! in \a key, to keep the setting separate. + bool readBoolEntry(const char *key, bool defaultValue); + + //! Helper like \ref readBoolEntry(const char *, bool), but for QString values. + QString readEntry(const char *key, const QString& defaultValue = QString::null); + + //! Helper, works like kapp->config()->writeEntry(const char*,bool) but if mode is Clipboard, + //! "Exporting" is replaced with "Copying" and "Export" is replaced with "Copy" + //! and "CSVFiles" is replaced with "CSVToClipboard" + //! in \a key, to keep the setting separate. + void writeEntry(const char *key, bool value); + + //! Helper like \ref writeEntry(const char *, bool), but for QString values. + void writeEntry(const char *key, const QString& value); + + //! Helper like \ref writeEntry(const char *, bool), but for deleting config entry. + void deleteEntry(const char *key); + + KexiCSVExport::Options m_options; +// Mode m_mode; +// int m_itemId; + KexiMainWindow* m_mainWin; + KexiStartupFileDialog* m_fileSavePage; + QWidget* m_exportOptionsPage; + KPushButton *m_showOptionsButton; + KPushButton *m_defaultsBtn; + QGroupBox* m_exportOptionsSection; + KexiCSVInfoLabel *m_infoLblFrom, *m_infoLblTo; + KexiCSVDelimiterWidget* m_delimiterWidget; + KexiCSVTextQuoteComboBox* m_textQuote; + KexiCharacterEncodingComboBox *m_characterEncodingCombo; + QCheckBox* m_addColumnNamesCheckBox, *m_alwaysUseCheckBox; + KexiDB::TableOrQuerySchema* m_tableOrQuery; + int m_rowCount; //!< Cached row count for a table/query. + bool m_rowCountDetermined : 1; + bool m_cancelled : 1; +}; + +#endif diff --git a/kexi/plugins/importexport/csv/kexicsvimportdialog.cpp b/kexi/plugins/importexport/csv/kexicsvimportdialog.cpp new file mode 100644 index 00000000..16a9d416 --- /dev/null +++ b/kexi/plugins/importexport/csv/kexicsvimportdialog.cpp @@ -0,0 +1,1662 @@ +/* This file is part of the KDE project + Copyright (C) 2005-2006 Jaroslaw Staniek <js@iidea.pl> + + This work is based on kspread/dialogs/kspread_dlg_csv.cc + and will be merged back with KOffice libraries. + + Copyright (C) 2002-2003 Norbert Andres <nandres@web.de> + Copyright (C) 2002-2003 Ariya Hidayat <ariya@kde.org> + Copyright (C) 2002 Laurent Montel <montel@kde.org> + Copyright (C) 1999 David Faure <faure@kde.org> + + 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 <qbuttongroup.h> +#include <qcheckbox.h> +#include <qclipboard.h> +#include <qlabel.h> +#include <qlineedit.h> +#include <qmime.h> +#include <qpushbutton.h> +#include <qradiobutton.h> +#include <qtable.h> +#include <qlayout.h> +#include <qfiledialog.h> +#include <qpainter.h> +#include <qtextcodec.h> +#include <qtimer.h> +#include <qfontmetrics.h> +#include <qtooltip.h> + +#include <kapplication.h> +#include <kdebug.h> +#include <kdialogbase.h> +#include <kfiledialog.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <kglobalsettings.h> +#include <kiconloader.h> +#include <kcharsets.h> +#include <knuminput.h> +#include <kprogress.h> +#include <kactivelabel.h> + +#include <kexiutils/identifier.h> +#include <kexiutils/utils.h> +#include <core/kexi.h> +#include <core/kexiproject.h> +#include <core/kexipart.h> +#include <core/kexipartinfo.h> +#include <core/keximainwindow.h> +#include <core/kexiguimsghandler.h> +#include <kexidb/connection.h> +#include <kexidb/tableschema.h> +#include <kexidb/transaction.h> +#include <widget/kexicharencodingcombobox.h> + +#include "kexicsvimportdialog.h" +#include "kexicsvwidgets.h" + +#ifdef Q_WS_WIN +#include <krecentdirs.h> +#include <windows.h> +#endif + +#if 0 +#include <kspread_cell.h> +#include <kspread_doc.h> +#include <kspread_sheet.h> +#include <kspread_undo.h> +#include <kspread_view.h> +#endif + +#define _IMPORT_ICON "table" /*todo: change to "file_import" or so*/ +#define _TEXT_TYPE 0 +#define _NUMBER_TYPE 1 +#define _DATE_TYPE 2 +#define _TIME_TYPE 3 +#define _DATETIME_TYPE 4 +#define _PK_FLAG 5 + +//extra: +#define _NO_TYPE_YET -1 //allows to accept a number of empty cells, before something non-empty +#define _FP_NUMBER_TYPE 255 //_NUMBER_TYPE variant +#define MAX_ROWS_TO_PREVIEW 100 //max 100 rows is reasonable +#define MAX_BYTES_TO_PREVIEW 10240 //max 10KB is reasonable +#define MAX_CHARS_TO_SCAN_WHILE_DETECTING_DELIMITER 4096 + +class KexiCSVImportDialogTable : public QTable +{ +public: + KexiCSVImportDialogTable( QWidget * parent = 0, const char * name = 0 ) + : QTable(parent, name) { + f = font(); + f.setBold(true); + } + virtual void paintCell( QPainter * p, int row, int col, const QRect & cr, bool selected, const QColorGroup & cg ) { + if (row==0) + p->setFont(f); + else + p->setFont(font()); + QTable::paintCell(p, row, col, cr, selected, cg); + } + virtual void setColumnWidth( int col, int w ) { + //make columns a bit wider + QTable::setColumnWidth( col, w + 16 ); + } + QFont f; +}; + +//! Helper used to temporary disable keyboard and mouse events +void installRecursiveEventFilter(QObject *filter, QObject *object) +{ + object->installEventFilter(filter); + + if (!object->children()) + return; + + QObjectList list = *object->children(); + for(QObject *obj = list.first(); obj; obj = list.next()) + installRecursiveEventFilter(filter, obj); +} + +KexiCSVImportDialog::KexiCSVImportDialog( Mode mode, KexiMainWindow* mainWin, + QWidget * parent, const char * name +) + : KDialogBase( + KDialogBase::Plain, + i18n( "Import CSV Data File" ) +//! @todo use "Paste CSV Data From Clipboard" caption for mode==Clipboard + , + (mode==File ? User1 : (ButtonCode)0) |Ok|Cancel, + Ok, + parent, + name ? name : "KexiCSVImportDialog", + true, + false, + KGuiItem( i18n("&Options")) + ), + m_mainWin(mainWin), + m_cancelled( false ), + m_adjustRows( true ), + m_startline( 0 ), + m_textquote( QString(KEXICSV_DEFAULT_FILE_TEXT_QUOTE)[0] ), + m_mode(mode), + m_prevSelectedCol(-1), + m_columnsAdjusted(false), + m_1stRowForFieldNamesDetected(false), + m_firstFillTableCall(true), + m_blockUserEvents(false), + m_primaryKeyColumn(-1), + m_dialogCancelled(false), + m_conn(0), + m_destinationTableSchema(0), + m_allRowsLoadedInPreview(false), + m_stoppedAt_MAX_BYTES_TO_PREVIEW(false) +{ + setWFlags(getWFlags() | Qt::WStyle_Maximize | Qt::WStyle_SysMenu); + hide(); + setButtonOK(KGuiItem( i18n("&Import..."), _IMPORT_ICON)); + + m_typeNames.resize(5); + m_typeNames[0] = i18n("text"); + m_typeNames[1] = i18n("number"); + m_typeNames[2] = i18n("date"); + m_typeNames[3] = i18n("time"); + m_typeNames[4] = i18n("date/time"); + + kapp->config()->setGroup("ImportExport"); + m_maximumRowsForPreview = kapp->config()->readNumEntry("MaximumRowsForPreviewInImportDialog", MAX_ROWS_TO_PREVIEW); + m_maximumBytesForPreview = kapp->config()->readNumEntry("MaximumBytesForPreviewInImportDialog", MAX_BYTES_TO_PREVIEW); + + m_pkIcon = SmallIcon("key"); + + m_uniquenessTest.setAutoDelete(true); + + setIcon(DesktopIcon(_IMPORT_ICON)); + setSizeGripEnabled( TRUE ); + +// m_encoding = QString::fromLatin1(KGlobal::locale()->encoding()); +// m_stripWhiteSpaceInTextValuesChecked = true; + m_file = 0; + m_inputStream = 0; + + QVBoxLayout *lyr = new QVBoxLayout(plainPage(), 0, KDialogBase::spacingHint(), "lyr"); + + m_infoLbl = new KexiCSVInfoLabel( + m_mode==File ? i18n("Preview of data from file:") + : i18n("Preview of data from clipboard:"), + plainPage() + ); + lyr->addWidget( m_infoLbl ); + + QWidget* page = new QFrame( plainPage(), "page" ); + QGridLayout *glyr= new QGridLayout( page, 4, 5, 0, KDialogBase::spacingHint(), "glyr"); + lyr->addWidget( page ); + + // Delimiter: comma, semicolon, tab, space, other + m_delimiterWidget = new KexiCSVDelimiterWidget(true /*lineEditOnBottom*/, page); + m_detectDelimiter = true; + glyr->addMultiCellWidget( m_delimiterWidget, 1, 2, 0, 0 ); + + QLabel *delimiterLabel = new QLabel(m_delimiterWidget, i18n("Delimiter:"), page); + delimiterLabel->setAlignment(Qt::AlignAuto | Qt::AlignBottom); + glyr->addMultiCellWidget( delimiterLabel, 0, 0, 0, 0 ); + + // Format: number, text, currency, + m_formatComboText = i18n( "Format for column %1:" ); + m_formatCombo = new KComboBox(page, "m_formatCombo"); + m_formatCombo->insertItem(i18n("Text")); + m_formatCombo->insertItem(i18n("Number")); + m_formatCombo->insertItem(i18n("Date")); + m_formatCombo->insertItem(i18n("Time")); + m_formatCombo->insertItem(i18n("Date/Time")); + glyr->addMultiCellWidget( m_formatCombo, 1, 1, 1, 1 ); + + m_formatLabel = new QLabel(m_formatCombo, "", page); + m_formatLabel->setAlignment(Qt::AlignAuto | Qt::AlignBottom); + glyr->addWidget( m_formatLabel, 0, 1 ); + + m_primaryKeyField = new QCheckBox( i18n( "Primary key" ), page, "m_primaryKeyField" ); + glyr->addWidget( m_primaryKeyField, 2, 1 ); + connect(m_primaryKeyField, SIGNAL(toggled(bool)), this, SLOT(slotPrimaryKeyFieldToggled(bool))); + + m_comboQuote = new KexiCSVTextQuoteComboBox( page ); + glyr->addWidget( m_comboQuote, 1, 2 ); + + TextLabel2 = new QLabel( m_comboQuote, i18n( "Text quote:" ), page, "TextLabel2" ); + TextLabel2->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Preferred ); + TextLabel2->setAlignment(Qt::AlignAuto | Qt::AlignBottom); + glyr->addWidget( TextLabel2, 0, 2 ); + + m_startAtLineSpinBox = new KIntSpinBox( page, "m_startAtLineSpinBox" ); + m_startAtLineSpinBox->setMinValue(1); + m_startAtLineSpinBox->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ); + m_startAtLineSpinBox->setMinimumWidth(QFontMetrics(m_startAtLineSpinBox->font()).width("8888888")); + glyr->addWidget( m_startAtLineSpinBox, 1, 3 ); + + m_startAtLineLabel = new QLabel( m_startAtLineSpinBox, "", + page, "TextLabel3" ); + m_startAtLineLabel->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Preferred ); + m_startAtLineLabel->setAlignment(Qt::AlignAuto | Qt::AlignBottom); + glyr->addWidget( m_startAtLineLabel, 0, 3 ); + + QSpacerItem* spacer_2 = new QSpacerItem( 0, 0, QSizePolicy::Minimum, QSizePolicy::Preferred ); + glyr->addItem( spacer_2, 0, 4 ); + + m_ignoreDuplicates = new QCheckBox( page, "m_ignoreDuplicates" ); + m_ignoreDuplicates->setText( i18n( "Ignore duplicated delimiters" ) ); + glyr->addMultiCellWidget( m_ignoreDuplicates, 2, 2, 2, 4 ); + + m_1stRowForFieldNames = new QCheckBox( page, "m_1stRowForFieldNames" ); + m_1stRowForFieldNames->setText( i18n( "First row contains column names" ) ); + glyr->addMultiCellWidget( m_1stRowForFieldNames, 3, 3, 2, 4 ); + + m_table = new KexiCSVImportDialogTable( plainPage(), "m_table" ); + lyr->addWidget( m_table ); + + m_table->setSizePolicy( QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding, 1, 1) ); + m_table->setNumRows( 0 ); + m_table->setNumCols( 0 ); + +/** @todo reuse Clipboard too! */ +/* +if ( m_mode == Clipboard ) + { + setCaption( i18n( "Inserting From Clipboard" ) ); + QMimeSource * mime = QApplication::clipboard()->data(); + if ( !mime ) + { + KMessageBox::information( this, i18n("There is no data in the clipboard.") ); + m_cancelled = true; + return; + } + + if ( !mime->provides( "text/plain" ) ) + { + KMessageBox::information( this, i18n("There is no usable data in the clipboard.") ); + m_cancelled = true; + return; + } + m_fileArray = QByteArray(mime->encodedData( "text/plain" ) ); + } + else if ( mode == File ) + {*/ + m_dateRegExp = QRegExp("(\\d{1,4})([/\\-\\.])(\\d{1,2})([/\\-\\.])(\\d{1,4})"); + m_timeRegExp1 = QRegExp("(\\d{1,2}):(\\d{1,2}):(\\d{1,2})"); + m_timeRegExp2 = QRegExp("(\\d{1,2}):(\\d{1,2})"); + m_fpNumberRegExp = QRegExp("[\\-]{0,1}\\d*[,\\.]\\d+"); + QString caption( i18n("Open CSV Data File") ); + + if (m_mode == File) { + QStringList mimetypes( csvMimeTypes() ); +#ifdef Q_WS_WIN + //! @todo remove + QString recentDir = KGlobalSettings::documentPath(); + m_fname = QFileDialog::getOpenFileName( + KFileDialog::getStartURL(":CSVImportExport", recentDir).path(), + KexiUtils::fileDialogFilterStrings(mimetypes, false), + page, "KexiCSVImportDialog", caption); + if ( !m_fname.isEmpty() ) { + //save last visited path + KURL url; + url.setPath( m_fname ); + if (url.isLocalFile()) + KRecentDirs::add(":CSVImportExport", url.directory()); + } +#else + m_fname = KFileDialog::getOpenFileName(":CSVImportExport", mimetypes.join(" "), + this, caption); +#endif + //cancel action ! + if ( m_fname.isEmpty() ) + { + actionButton( Ok )->setEnabled( false ); + m_cancelled = true; + if (parentWidget()) + parentWidget()->raise(); + return; + } + } + else if (m_mode == Clipboard) { + QCString subtype("plain"); + m_clipboardData = QApplication::clipboard()->text(subtype, QClipboard::Clipboard); +/* debug + for (int i=0;QApplication::clipboard()->data(QClipboard::Clipboard)->format(i);i++) + kdDebug() << i << ": " + << QApplication::clipboard()->data(QClipboard::Clipboard)->format(i) << endl; +*/ + } + else { + return; + } + + m_loadingProgressDlg = 0; + m_importingProgressDlg = 0; + if (m_mode == File) { + m_loadingProgressDlg = new KProgressDialog( + this, "m_loadingProgressDlg", i18n("Loading CSV Data"), i18n("Loading CSV Data from \"%1\"...") + .arg(QDir::convertSeparators(m_fname)), true); + m_loadingProgressDlg->progressBar()->setTotalSteps( m_maximumRowsForPreview+1 ); + m_loadingProgressDlg->show(); + } + + if (m_mode==Clipboard) { + m_infoLbl->setIcon("editpaste"); + } + //updateRowCountInfo(); + + m_table->setSelectionMode(QTable::NoSelection); + + connect(m_formatCombo, SIGNAL(activated(int)), + this, SLOT(formatChanged(int))); + connect(m_delimiterWidget, SIGNAL(delimiterChanged(const QString&)), + this, SLOT(delimiterChanged(const QString&))); + connect(m_startAtLineSpinBox, SIGNAL(valueChanged ( int )), + this, SLOT(startlineSelected(int))); + connect(m_comboQuote, SIGNAL(activated(int)), + this, SLOT(textquoteSelected(int))); + connect(m_table, SIGNAL(currentChanged(int, int)), + this, SLOT(currentCellChanged(int, int))); + connect(m_table, SIGNAL(valueChanged(int,int)), + this, SLOT(cellValueChanged(int,int))); + connect(m_ignoreDuplicates, SIGNAL(stateChanged(int)), + this, SLOT(ignoreDuplicatesChanged(int))); + connect(m_1stRowForFieldNames, SIGNAL(stateChanged(int)), + this, SLOT(slot1stRowForFieldNamesChanged(int))); + + connect(this, SIGNAL(user1Clicked()), this, SLOT(optionsButtonClicked())); + + installRecursiveEventFilter(this, this); + + initLater(); +} + +KexiCSVImportDialog::~KexiCSVImportDialog() +{ + delete m_file; +} + +void KexiCSVImportDialog::initLater() +{ + if (!openData()) + return; + +// delimiterChanged(detectedDelimiter); // this will cause fillTable() + m_columnsAdjusted = false; + fillTable(); + delete m_loadingProgressDlg; + m_loadingProgressDlg = 0; + if (m_dialogCancelled) { +// m_loadingProgressDlg->hide(); + // m_loadingProgressDlg->close(); + QTimer::singleShot(0, this, SLOT(reject())); + return; + } + + currentCellChanged(0, 0); + +// updateGeometry(); + adjustSize(); + KDialog::centerOnScreen( this ); + + if (m_loadingProgressDlg) + m_loadingProgressDlg->hide(); + show(); + m_table->setFocus(); +} + +bool KexiCSVImportDialog::openData() +{ + if (m_mode!=File) //data already loaded, no encoding stuff needed + return true; + + delete m_inputStream; + m_inputStream = 0; + if (m_file) { + m_file->close(); + delete m_file; + } + m_file = new QFile(m_fname); + if (!m_file->open(IO_ReadOnly)) + { + m_file->close(); + delete m_file; + m_file = 0; + KMessageBox::sorry( this, i18n("Cannot open input file <nobr>\"%1\"</nobr>.") + .arg(QDir::convertSeparators(m_fname)) ); + actionButton( Ok )->setEnabled( false ); + m_cancelled = true; + if (parentWidget()) + parentWidget()->raise(); + return false; + } + return true; +} + +bool KexiCSVImportDialog::cancelled() const +{ + return m_cancelled; +} + +void KexiCSVImportDialog::fillTable() +{ + KexiUtils::WaitCursor wc(true); + repaint(); + m_blockUserEvents = true; + QPushButton *pb = actionButton(KDialogBase::Cancel); + if (pb) + pb->setEnabled(true); //allow to cancel + KexiUtils::WaitCursor wait; + + if (m_table->numRows()>0) //to accept editor + m_table->setCurrentCell(0,0); + + int row, column, maxColumn; + QString field = QString::null; + + for (row = 0; row < m_table->numRows(); ++row) + for (column = 0; column < m_table->numCols(); ++column) + m_table->clearCell(row, column); + + m_detectedTypes.clear(); + m_detectedTypes.resize(1024, _NO_TYPE_YET);//_TEXT_TYPE); + m_uniquenessTest.clear(); + m_uniquenessTest.resize(1024); + m_1stRowForFieldNamesDetected = true; + + if (true != loadRows(field, row, column, maxColumn, true)) + return; + + m_1stRowForFieldNamesDetected = false; + + // file with only one line without '\n' + if (field.length() > 0) + { + setText(row - m_startline, column, field, true); + ++row; + field = QString::null; + } + + adjustRows( row - m_startline - (m_1stRowForFieldNames->isChecked()?1:0) ); + + maxColumn = QMAX( maxColumn, column ); + m_table->setNumCols(maxColumn); + + for (column = 0; column < m_table->numCols(); ++column) + { +// QString header = m_table->horizontalHeader()->label(column); +// if (header != i18n("Text") && header != i18n("Number") && +// header != i18n("Date") && header != i18n("Currency")) +// const int detectedType = m_detectedTypes[column+1]; +// m_table->horizontalHeader()->setLabel(column, m_typeNames[ detectedType ]); //i18n("Text")); + updateColumnText(column); + if (!m_columnsAdjusted) + m_table->adjustColumn(column); + } + m_columnsAdjusted = true; + + if (m_primaryKeyColumn>=0 && m_primaryKeyColumn<m_table->numCols()) { + if (_NUMBER_TYPE != m_detectedTypes[ m_primaryKeyColumn ]) { + m_primaryKeyColumn = -1; + } + } + + m_prevSelectedCol = -1; + m_table->setCurrentCell(0,0); + currentCellChanged(0, 0); + if (m_primaryKeyColumn != -1) + m_table->setPixmap(0, m_primaryKeyColumn, m_pkIcon); + + const int count = QMAX(0, m_table->numRows()-1+m_startline); + m_allRowsLoadedInPreview = count < m_maximumRowsForPreview && !m_stoppedAt_MAX_BYTES_TO_PREVIEW; + if (m_allRowsLoadedInPreview) { + m_startAtLineSpinBox->setMaxValue(count); + m_startAtLineSpinBox->setValue(m_startline+1); + } + m_startAtLineLabel->setText(i18n( "Start at line%1:").arg( + m_allRowsLoadedInPreview ? QString(" (1-%1)").arg(count) + : QString::null //we do not know what's real count + )); + updateRowCountInfo(); + + m_blockUserEvents = false; + repaint(); + m_table->verticalScrollBar()->repaint();//avoid missing repaint + m_table->horizontalScrollBar()->repaint();//avoid missing repaint +} + +QString KexiCSVImportDialog::detectDelimiterByLookingAtFirstBytesOfFile(QTextStream& inputStream) +{ + m_file->at(0); + + // try to detect delimiter + // \t has priority, then ; then , + const QIODevice::Offset origOffset = inputStream.device()->at(); + QChar c, prevChar=0; + int detectedDelimiter = 0; + bool insideQuote = false; + + //characters by priority + const int CH_TAB_AFTER_QUOTE = 500; + const int CH_SEMICOLON_AFTER_QUOTE = 499; + const int CH_COMMA_AFTER_QUOTE = 498; + const int CH_TAB = 200; // \t + const int CH_SEMICOLON = 199; // ; + const int CH_COMMA = 198; // , + + QValueList<int> tabsPerLine, semicolonsPerLine, commasPerLine; + int tabs = 0, semicolons = 0, commas = 0; + int line = 0; + for (uint i=0; !inputStream.atEnd() && i < MAX_CHARS_TO_SCAN_WHILE_DETECTING_DELIMITER; i++) { + (*m_inputStream) >> c; // read one char + if (prevChar=='"') { + if (c!='"') //real quote (not double "") + insideQuote = !insideQuote; + } + if (insideQuote) { + prevChar = c; + continue; + } + if (c==' ') + continue; + if (c=='\n') {//end of line + //remember # of tabs/semicolons/commas in this line + tabsPerLine += tabs; + tabs = 0; + semicolonsPerLine += semicolons; + semicolons = 0; + commasPerLine += commas; + commas = 0; + line++; + } + else if (c=='\t') { + tabs++; + detectedDelimiter = QMAX( prevChar=='"' ? CH_TAB_AFTER_QUOTE : CH_TAB, detectedDelimiter ); + } + else if (c==';') { + semicolons++; + detectedDelimiter = QMAX( prevChar=='"' ? CH_SEMICOLON_AFTER_QUOTE : CH_SEMICOLON, detectedDelimiter ); + } + else if (c==',') { + commas++; + detectedDelimiter = QMAX( prevChar=='"' ? CH_COMMA_AFTER_QUOTE : CH_COMMA, detectedDelimiter ); + } + prevChar = c; + } + + inputStream.device()->at(origOffset); //restore orig. offset + + //now, try to find a delimiter character that exists the same number of times in all the checked lines + //this detection method has priority over others + QValueList<int>::ConstIterator it; + if (tabsPerLine.count()>1) { + tabs = tabsPerLine.isEmpty() ? 0 : tabsPerLine.first(); + for (it=tabsPerLine.constBegin(); it!=tabsPerLine.constEnd(); ++it) { + if (tabs != *it) + break; + } + if (tabs>0 && it==tabsPerLine.constEnd()) + return "\t"; + } + if (semicolonsPerLine.count()>1) { + semicolons = semicolonsPerLine.isEmpty() ? 0 : semicolonsPerLine.first(); + for (it=semicolonsPerLine.constBegin(); it!=semicolonsPerLine.constEnd(); ++it) { + if (semicolons != *it) + break; + } + if (semicolons > 0 && it==semicolonsPerLine.constEnd()) + return ";"; + } + if (commasPerLine.count()>1) { + commas = commasPerLine.first(); + for (it=commasPerLine.constBegin(); it!=commasPerLine.constEnd(); ++it) { + if (commas != *it) + break; + } + if (commas > 0 && it==commasPerLine.constEnd()) + return ","; + } + //now return the winning character by looking at CH_* symbol + if (detectedDelimiter == CH_TAB_AFTER_QUOTE || detectedDelimiter == CH_TAB) + return "\t"; + if (detectedDelimiter == CH_SEMICOLON_AFTER_QUOTE || detectedDelimiter == CH_SEMICOLON) + return ";"; + if (detectedDelimiter == CH_COMMA_AFTER_QUOTE || detectedDelimiter == CH_COMMA) + return ","; + + return KEXICSV_DEFAULT_FILE_DELIMITER; //<-- default +} + +tristate KexiCSVImportDialog::loadRows(QString &field, int &row, int &column, int &maxColumn, + bool inGUI) +{ + enum { S_START, S_QUOTED_FIELD, S_MAYBE_END_OF_QUOTED_FIELD, S_END_OF_QUOTED_FIELD, + S_MAYBE_NORMAL_FIELD, S_NORMAL_FIELD } state = S_START; + field = QString::null; + const bool ignoreDups = m_ignoreDuplicates->isChecked(); + bool lastCharDelimiter = false; + bool nextRow = false; + row = column = 1; + maxColumn = 0; + QChar x; + const bool hadInputStream = m_inputStream!=0; + delete m_inputStream; + if ( m_mode == Clipboard ) { + m_inputStream = new QTextStream(m_clipboardData, IO_ReadOnly); + if (!hadInputStream) + m_delimiterWidget->setDelimiter(KEXICSV_DEFAULT_CLIPBOARD_DELIMITER); + } + else { + m_file->at(0); //always seek at 0 because loadRows() is called many times + m_inputStream = new QTextStream(m_file); + if (m_options.defaultEncodingExplicitySet) { + QTextCodec *codec = KGlobal::charsets()->codecForName(m_options.encoding); + if (codec) + m_inputStream->setCodec(codec); //QTextCodec::codecForName("CP1250")); + } + if (m_detectDelimiter) { + const QString delimiter( detectDelimiterByLookingAtFirstBytesOfFile(*m_inputStream) ); + if (m_delimiterWidget->delimiter() != delimiter) + m_delimiterWidget->setDelimiter( delimiter ); + } + } + const QChar delimiter(m_delimiterWidget->delimiter()[0]); + m_stoppedAt_MAX_BYTES_TO_PREVIEW = false; + int progressStep = 0; + if (m_importingProgressDlg) + progressStep = QMAX( 1, m_importingProgressDlg->progressBar()->totalSteps()/200 ); + int offset = 0; + for (;!m_inputStream->atEnd(); offset++) + { +//disabled: this breaks wide spreadsheets +// if (column >= m_maximumRowsForPreview) +// return true; + + if (m_importingProgressDlg && ((offset % progressStep) < 5)) { + //update progr. bar dlg on final exporting + m_importingProgressDlg->progressBar()->setValue(offset); + qApp->processEvents(); + if (m_importingProgressDlg->wasCancelled()) { + delete m_importingProgressDlg; + m_importingProgressDlg = 0; + return ::cancelled; + } + } + + (*m_inputStream) >> x; // read one char + + if (x == '\r') { + continue; // eat '\r', to handle RFC-compliant files + } + if (offset==0 && x.unicode()==0xfeff) { + // Ignore BOM, the "Byte Order Mark" + // (http://en.wikipedia.org/wiki/Byte_Order_Mark, // http://www.unicode.org/charts/PDF/UFFF0.pdf) + // Probably fixed in Qt4. + continue; + } + + switch (state) + { + case S_START : + if (x == m_textquote) + { + state = S_QUOTED_FIELD; + } + else if (x == delimiter) + { + setText(row - m_startline, column, field, inGUI); + field = QString::null; + if ((ignoreDups == false) || (lastCharDelimiter == false)) + ++column; + lastCharDelimiter = true; + } + else if (x == '\n') + { + if (!inGUI) { + //fill remaining empty fields (database wants them explicitly) + for (int additionalColumn = column; additionalColumn <= maxColumn; additionalColumn++) { + setText(row - m_startline, additionalColumn, QString::null, inGUI); + } + } + nextRow = true; + maxColumn = QMAX( maxColumn, column ); + column = 1; + } + else + { + field += x; + state = S_MAYBE_NORMAL_FIELD; + } + break; + case S_QUOTED_FIELD : + if (x == m_textquote) + { + state = S_MAYBE_END_OF_QUOTED_FIELD; + } +/*allow \n inside quoted fields + else if (x == '\n') + { + setText(row - m_startline, column, field, inGUI); + field = ""; + if (x == '\n') + { + nextRow = true; + maxColumn = QMAX( maxColumn, column ); + column = 1; + } + else + { + if ((ignoreDups == false) || (lastCharDelimiter == false)) + ++column; + lastCharDelimiter = true; + } + state = S_START; + }*/ + else + { + field += x; + } + break; + case S_MAYBE_END_OF_QUOTED_FIELD : + if (x == m_textquote) + { + field += x; //no, this was just escaped quote character + state = S_QUOTED_FIELD; + } + else if (x == delimiter || x == '\n') + { + setText(row - m_startline, column, field, inGUI); + field = QString::null; + if (x == '\n') + { + nextRow = true; + maxColumn = QMAX( maxColumn, column ); + column = 1; + } + else + { + if ((ignoreDups == false) || (lastCharDelimiter == false)) + ++column; + lastCharDelimiter = true; + } + state = S_START; + } + else + { + state = S_END_OF_QUOTED_FIELD; + } + break; + case S_END_OF_QUOTED_FIELD : + if (x == delimiter || x == '\n') + { + setText(row - m_startline, column, field, inGUI); + field = QString::null; + if (x == '\n') + { + nextRow = true; + maxColumn = QMAX( maxColumn, column ); + column = 1; + } + else + { + if ((ignoreDups == false) || (lastCharDelimiter == false)) + ++column; + lastCharDelimiter = true; + } + state = S_START; + } + else + { + state = S_END_OF_QUOTED_FIELD; + } + break; + case S_MAYBE_NORMAL_FIELD : + if (x == m_textquote) + { + field = QString::null; + state = S_QUOTED_FIELD; + break; + } + case S_NORMAL_FIELD : + if (x == delimiter || x == '\n') + { + setText(row - m_startline, column, field, inGUI); + field = QString::null; + if (x == '\n') + { + nextRow = true; + maxColumn = QMAX( maxColumn, column ); + column = 1; + } + else + { + if ((ignoreDups == false) || (lastCharDelimiter == false)) + ++column; + lastCharDelimiter = true; + } + state = S_START; + } + else + { + field += x; + } + } + if (x != delimiter) + lastCharDelimiter = false; + + if (nextRow) { + if (!inGUI && row==1 && m_1stRowForFieldNames->isChecked()) { + // do not save to the database 1st row if it contains column names + m_importingStatement->clearArguments(); + } + else if (!saveRow(inGUI)) + return false; + + ++row; + } + + if (m_firstFillTableCall && row==2 + && !m_1stRowForFieldNames->isChecked() && m_1stRowForFieldNamesDetected) + { + //'1st row for field name' flag detected: reload table + m_1stRowForFieldNamesDetected = false; + m_table->setNumRows( 0 ); + m_firstFillTableCall = false; //this trick is allowed only once, on startup + m_1stRowForFieldNames->setChecked(true); //this will reload table + //slot1stRowForFieldNamesChanged(1); + m_blockUserEvents = false; + repaint(); + return false; + } + + if (!m_importingProgressDlg && row % 20 == 0) { + qApp->processEvents(); + //only for GUI mode: + if (!m_firstFillTableCall && m_loadingProgressDlg && m_loadingProgressDlg->wasCancelled()) { + delete m_loadingProgressDlg; + m_loadingProgressDlg = 0; + m_dialogCancelled = true; + reject(); + return false; + } + } + + if (!m_firstFillTableCall && m_loadingProgressDlg) { + m_loadingProgressDlg->progressBar()->setValue(QMIN(m_maximumRowsForPreview, row)); + } + + if ( inGUI && row > (m_maximumRowsForPreview + (m_1stRowForFieldNamesDetected?1:0)) ) { + kexipluginsdbg << "KexiCSVImportDialog::fillTable() loading stopped at row #" + << m_maximumRowsForPreview << endl; + break; + } + if (nextRow) { + nextRow = false; + //additional speedup: stop processing now if too many bytes were loaded for preview + kexipluginsdbg << offset << endl; + if (inGUI && offset >= m_maximumBytesForPreview && row >= 2) { + m_stoppedAt_MAX_BYTES_TO_PREVIEW = true; + return true; + } + } + } + return true; +} + +void KexiCSVImportDialog::updateColumnText(int col) +{ + QString colName; + if (col<(int)m_columnNames.count() && (m_1stRowForFieldNames->isChecked() || m_changedColumnNames[col])) + colName = m_columnNames[ col ]; + if (colName.isEmpty()) { + colName = i18n("Column %1").arg(col+1); //will be changed to a valid identifier on import + m_changedColumnNames[ col ] = false; + } + int detectedType = m_detectedTypes[col]; + if (detectedType==_FP_NUMBER_TYPE) + detectedType=_NUMBER_TYPE; //we're simplifying that for now + else if (detectedType==_NO_TYPE_YET) { + m_detectedTypes[col]=_TEXT_TYPE; //entirely empty column + detectedType=_TEXT_TYPE; + } + m_table->horizontalHeader()->setLabel(col, + i18n("Column %1").arg(col+1) + " \n(" + m_typeNames[ detectedType ] + ") "); + m_table->setText(0, col, colName); + m_table->horizontalHeader()->adjustHeaderSize(); + + //check uniqueness + QValueList<int> *list = m_uniquenessTest[col]; + if (m_primaryKeyColumn==-1 && list && !list->isEmpty()) { + qHeapSort(*list); + QValueList<int>::ConstIterator it=list->constBegin(); + int prevValue = *it; + ++it; + for(; it!=list->constEnd() && prevValue!=(*it); ++it) + prevValue=(*it); + if (it!=list->constEnd()) { + //duplicates: + list->clear(); + } + else { + //a candidate for PK (autodetected)! + if (-1==m_primaryKeyColumn) { + m_primaryKeyColumn=col; + } + } + } + if (list) //not needed now: conserve memory + list->clear(); +} + +void KexiCSVImportDialog::detectTypeAndUniqueness(int row, int col, const QString& text) +{ + int intValue; + const int type = m_detectedTypes[col]; + if (row==1 || type!=_TEXT_TYPE) { + bool found = false; + if (text.isEmpty() && type==_NO_TYPE_YET) + found = true; //real type should be found later + //detect type because it's 1st row or all prev. rows were not text + //-FP number? (trying before "number" type is a must) + if (!found && (row==1 || type==_NUMBER_TYPE || type==_FP_NUMBER_TYPE || type==_NO_TYPE_YET)) { + bool ok = text.isEmpty() || m_fpNumberRegExp.exactMatch(text); + //if (!ok) + // text.toDouble(&ok); + if (ok && (row==1 || type==_NUMBER_TYPE || type==_FP_NUMBER_TYPE || type==_NO_TYPE_YET)) { + m_detectedTypes[col]=_FP_NUMBER_TYPE; + found = true; //yes + } + } + //-number? + if (!found && (row==1 || type==_NUMBER_TYPE || type==_NO_TYPE_YET)) { + bool ok = text.isEmpty();//empty values allowed + if (!ok) + intValue = text.toInt(&ok); + if (ok && (row==1 || type==_NO_TYPE_YET)) { + m_detectedTypes[col]=_NUMBER_TYPE; + found = true; //yes + } + } + //-date? + if (!found && (row==1 || type==_DATE_TYPE || type==_NO_TYPE_YET)) { + if ((row==1 || type==_NO_TYPE_YET) + && (text.isEmpty() || m_dateRegExp.exactMatch(text))) + { + m_detectedTypes[col]=_DATE_TYPE; + found = true; //yes + } + } + //-time? + if (!found && (row==1 || type==_TIME_TYPE || type==_NO_TYPE_YET)) { + if ((row==1 || type==_NO_TYPE_YET) + && (text.isEmpty() || m_timeRegExp1.exactMatch(text) || m_timeRegExp2.exactMatch(text))) + { + m_detectedTypes[col]=_TIME_TYPE; + found = true; //yes + } + } + //-date/time? + if (!found && (row==1 || type==_TIME_TYPE || type==_NO_TYPE_YET)) { + if (row==1 || type==_NO_TYPE_YET) { + bool detected = text.isEmpty(); + if (!detected) { + const QStringList dateTimeList( QStringList::split(" ", text) ); + bool ok = dateTimeList.count()>=2; +//! @todo also support ISODateTime's "T" separator? +//! @todo also support timezones? + if (ok) { + //try all combinations + QString datePart( dateTimeList[0].stripWhiteSpace() ); + QString timePart( dateTimeList[1].stripWhiteSpace() ); + ok = m_dateRegExp.exactMatch(datePart) + && (m_timeRegExp1.exactMatch(timePart) || m_timeRegExp2.exactMatch(timePart)); + } + detected = ok; + } + if (detected) { + m_detectedTypes[col]=_DATETIME_TYPE; + found = true; //yes + } + } + } + if (!found && type==_NO_TYPE_YET && !text.isEmpty()) { + //eventually, a non-emptytext after a while + m_detectedTypes[col]=_TEXT_TYPE; + found = true; //yes + } + //default: text type (already set) + } + //check uniqueness for this value + QValueList<int> *list = m_uniquenessTest[col]; + if (row==1 && (!list || !list->isEmpty()) && !text.isEmpty() && _NUMBER_TYPE == m_detectedTypes[col]) { + if (!list) { + list = new QValueList<int>(); + m_uniquenessTest.insert(col, list); + } + list->append( intValue ); + } + else { + //the value is empty or uniqueness test failed in the past + if (list && !list->isEmpty()) + list->clear(); //indicate that uniqueness test failed + } +} + +bool KexiCSVImportDialog::parseDate(const QString& text, QDate& date) +{ + if (!m_dateRegExp.exactMatch(text)) + return false; + //dddd - dd - dddd + //1 2 3 4 5 <- pos + const int d1 = m_dateRegExp.cap(1).toInt(), d3 = m_dateRegExp.cap(3).toInt(), d5 = m_dateRegExp.cap(5).toInt(); + if (m_dateRegExp.cap(2)=="/") //probably separator for american format mm/dd/yyyy + date = QDate(d5, d1, d3); + else { + if (d5 > 31) //d5 == year + date = QDate(d5, d3, d1); + else //d1 == year + date = QDate(d1, d3, d5); + } + return date.isValid(); +} + +bool KexiCSVImportDialog::parseTime(const QString& text, QTime& time) +{ + time = QTime::fromString(text, Qt::ISODate); //same as m_timeRegExp1 + if (time.isValid()) + return true; + if (m_timeRegExp2.exactMatch(text)) { //hh:mm:ss + time = QTime(m_timeRegExp2.cap(1).toInt(), m_timeRegExp2.cap(3).toInt(), m_timeRegExp2.cap(5).toInt()); + return true; + } + return false; +} + +void KexiCSVImportDialog::setText(int row, int col, const QString& text, bool inGUI) +{ + if (!inGUI) { + //save text directly to database buffer + if (col==1) { //1st col + m_importingStatement->clearArguments(); + if (m_implicitPrimaryKeyAdded) + *m_importingStatement << QVariant(); //id will be autogenerated here + } + const int detectedType = m_detectedTypes[col-1]; + if (detectedType==_NUMBER_TYPE) { + *m_importingStatement << ( text.isEmpty() ? QVariant() : text.toInt() ); +//! @todo what about time and float/double types and different integer subtypes? + } + else if (detectedType==_FP_NUMBER_TYPE) { + //replace ',' with '.' + QCString t(text.latin1()); + const int textLen = t.length(); + for (int i=0; i<textLen; i++) { + if (t.at(i)==',') { + t.at(i) = '.'; + break; + } + } + *m_importingStatement << ( t.isEmpty() ? QVariant() : t.toDouble() ); + } + else if (detectedType==_DATE_TYPE) { + QDate date; + if (parseDate(text, date)) + *m_importingStatement << date; + } + else if (detectedType==_TIME_TYPE) { + QTime time; + if (parseTime(text, time)) + *m_importingStatement << time; + } + else if (detectedType==_DATETIME_TYPE) { + QStringList dateTimeList( QStringList::split(" ", text) ); + if (dateTimeList.count()<2) + dateTimeList = QStringList::split("T", text); //also support ISODateTime's "T" separator +//! @todo also support timezones? + if (dateTimeList.count()>=2) { + QString datePart( dateTimeList[0].stripWhiteSpace() ); + QDate date; + if (parseDate(datePart, date)) { + QString timePart( dateTimeList[1].stripWhiteSpace() ); + QTime time; + if (parseTime(timePart, time)) + *m_importingStatement << QDateTime(date, time); + } + } + } + else //_TEXT_TYPE and the rest + *m_importingStatement << (m_options.stripWhiteSpaceInTextValuesChecked ? text.stripWhiteSpace() : text); + return; + } + //save text to GUI (table view) + if (m_table->numCols() < col) { + m_table->setNumCols(col); + if ((int)m_columnNames.size() < m_table->numCols()) { + m_columnNames.resize(m_table->numCols()+10); + m_changedColumnNames.resize(m_table->numCols()+10); + } + } + + if (m_1stRowForFieldNames->isChecked()) { + if ((row+m_startline)==1) {//this is for column name + if ((col-1) < (int)m_changedColumnNames.size() && false==m_changedColumnNames[col-1]) { + //this column has no custom name entered by a user + //-get the name from the data cell + QString colName(text.simplifyWhiteSpace()); + if (!colName.isEmpty()) { + if (colName.left(1)>="0" && colName.left(1)<="9") + colName.prepend(i18n("Column")+" "); + m_columnNames[ col-1 ] = colName; + } + } + return; + } + } + else { + if ((row+m_startline)==1) {//this row is for column name + if (m_1stRowForFieldNamesDetected && !m_1stRowForFieldNames->isChecked()) { + QString f( text.simplifyWhiteSpace() ); + if (f.isEmpty() || !f[0].isLetter()) + m_1stRowForFieldNamesDetected = false; //this couldn't be a column name + } + } + row++; //1st row was for column names + } + + if (row < 2) // skipped by the user + return; + + if (m_table->numRows() < row) { +// if (m_maximumRowsForPreview >= row+100) + m_table->setNumRows(row+100); /* We add more rows at a time to limit recalculations */ + //else +// m_table->setNumRows(m_maximumRowsForPreview); + m_table->verticalHeader()->setLabel(0, i18n("Column name")+" "); + m_adjustRows=true; + } + + m_table->setText(row - 1, col - 1, (m_options.stripWhiteSpaceInTextValuesChecked ? text.stripWhiteSpace() : text)); + m_table->verticalHeader()->setLabel(row-1, QString::number(row-1)); + + detectTypeAndUniqueness(row-1, col-1, text); +} + +bool KexiCSVImportDialog::saveRow(bool inGUI) +{ + if (inGUI) { + //nothing to do + return true; + } + //save db buffer + bool res = m_importingStatement->execute(); +//todo: move + m_importingStatement->clearArguments(); + return res; +// return m_conn->insertRecord(*m_destinationTableSchema, m_dbRowBuffer); +} + +void KexiCSVImportDialog::adjustRows(int iRows) +{ + if (m_adjustRows) + { + m_table->setNumRows( iRows ); + m_adjustRows=false; + for (int i = 0; i<iRows; i++) + m_table->adjustRow(i); + } +} + +void KexiCSVImportDialog::formatChanged(int id) +{ + if (id==_PK_FLAG) { + if (m_primaryKeyColumn>=0 && m_primaryKeyColumn<m_table->numCols()) { + m_table->setPixmap(0, m_primaryKeyColumn, QPixmap()); + } + if (m_primaryKeyField->isChecked()) { + m_primaryKeyColumn = m_table->currentColumn(); + m_table->setPixmap(0, m_primaryKeyColumn, m_pkIcon); + } + else + m_primaryKeyColumn = -1; + return; + } + else { + m_detectedTypes[m_table->currentColumn()]=id; + m_primaryKeyField->setEnabled( _NUMBER_TYPE == id ); + m_primaryKeyField->setChecked( m_primaryKeyColumn == m_table->currentColumn() && m_primaryKeyField->isEnabled() ); + } + updateColumnText(m_table->currentColumn()); +} + +void KexiCSVImportDialog::delimiterChanged(const QString& delimiter) +{ + Q_UNUSED(delimiter); + m_columnsAdjusted = false; + m_detectDelimiter = false; //selected by hand: do not detect in the future + //delayed, otherwise combobox won't be repainted + fillTableLater(); +} + +void KexiCSVImportDialog::textquoteSelected(int) +{ + const QString tq(m_comboQuote->textQuote()); + if (tq.isEmpty()) + m_textquote = 0; + else + m_textquote = tq[0]; + + kexipluginsdbg << "KexiCSVImportDialog::textquoteSelected(): " << m_textquote << endl; + + //delayed, otherwise combobox won't be repainted + fillTableLater(); +} + +void KexiCSVImportDialog::fillTableLater() +{ + m_table->setNumRows( 0 ); + QTimer::singleShot(10, this, SLOT(fillTable())); +} + +void KexiCSVImportDialog::startlineSelected(int startline) +{ +// const int startline = line.toInt() - 1; + if (m_startline == (startline-1)) + return; + m_startline = startline-1; + m_adjustRows=true; + fillTable(); + m_table->setFocus(); +} + +void KexiCSVImportDialog::currentCellChanged(int, int col) +{ + if (m_prevSelectedCol==col) + return; + m_prevSelectedCol = col; + int type = m_detectedTypes[col]; + if (type==_FP_NUMBER_TYPE) + type=_NUMBER_TYPE; //we're simplifying that for now + + m_formatCombo->setCurrentItem( type ); + m_formatLabel->setText( m_formatComboText.arg(col+1) ); + m_primaryKeyField->setEnabled( _NUMBER_TYPE == m_detectedTypes[col]); + m_primaryKeyField->blockSignals(true); //block to disable executing slotPrimaryKeyFieldToggled() + m_primaryKeyField->setChecked( m_primaryKeyColumn == col ); + m_primaryKeyField->blockSignals(false); +} + +void KexiCSVImportDialog::cellValueChanged(int row,int col) +{ + if (row==0) {//column name has changed + m_columnNames[ col ] = m_table->text(row, col); + m_changedColumnNames.setBit( col ); + } +} + +void KexiCSVImportDialog::accept() +{ +//! @todo MOVE MOST OF THIS TO CORE/ (KexiProject?) after KexiDialogBase code is moved to non-gui place + + KexiGUIMessageHandler msg; //! @todo make it better integrated with main window + + const uint numRows( m_table->numRows() ); + if (numRows == 0) + return; //impossible + + if (numRows == 1) { + if (KMessageBox::No == KMessageBox::questionYesNo(this, + i18n("Data set contains no rows. Do you want to import empty table?"))) + return; + } + + KexiProject* project = m_mainWin->project(); + if (!project) { + msg.showErrorMessage(i18n("No project available.")); + return; + } + m_conn = project->dbConnection(); //cache this pointer + if (!m_conn) { + msg.showErrorMessage(i18n("No database connection available.")); + return; + } + KexiPart::Part *part = Kexi::partManager().partForMimeType("kexi/table"); + if (!part) { + msg.showErrorMessage(&Kexi::partManager()); + return; + } + + //get suggested name based on the file name + QString suggestedName; + if (m_mode==File) { + suggestedName = KURL::fromPathOrURL(m_fname).fileName(); + //remove extension + if (!suggestedName.isEmpty()) { + const int idx = suggestedName.findRev("."); + if (idx!=-1) + suggestedName = suggestedName.mid(0, idx ).simplifyWhiteSpace(); + } + } + + //-new part item + KexiPart::Item* partItemForSavedTable = project->createPartItem(part->info(), suggestedName); + if (!partItemForSavedTable) { + // msg.showErrorMessage(project); + return; + } + +#define _ERR \ + { project->deleteUnstoredItem(partItemForSavedTable); \ + m_conn = 0; \ + delete m_destinationTableSchema; \ + m_destinationTableSchema = 0; \ + return; } + + //-ask for table name/title + // (THIS IS FROM KexiMainWindowImpl::saveObject()) + bool allowOverwriting = true; + tristate res = m_mainWin->getNewObjectInfo( partItemForSavedTable, part, allowOverwriting ); + if (~res || !res) { + //! @todo: err + _ERR; + } + //(allowOverwriting is now set to true, if user accepts overwriting, + // and overwriting will be needed) + +// KexiDB::SchemaData sdata(part->info()->projectPartID()); +// sdata.setName( partItem->name() ); + + //-create table schema (and thus schema object) + //-assign information (THIS IS FROM KexiDialogBase::storeNewData()) + m_destinationTableSchema = new KexiDB::TableSchema(partItemForSavedTable->name()); + m_destinationTableSchema->setCaption( partItemForSavedTable->caption() ); + m_destinationTableSchema->setDescription( partItemForSavedTable->description() ); + const uint numCols( m_table->numCols() ); + + m_implicitPrimaryKeyAdded = false; + //add PK if user wanted it + int msgboxResult; + if (m_primaryKeyColumn==-1 + && KMessageBox::No != (msgboxResult = KMessageBox::questionYesNoCancel(this, + i18n("No Primary Key (autonumber) has been defined.\n" + "Should it be automatically defined on import (recommended)?\n\n" + "Note: An imported table without a Primary Key may not be editable (depending on database type)."), + QString::null, KGuiItem(i18n("Add Database Primary Key to a Table", "Add Primary Key"), "key"), + KGuiItem(i18n("Do Not Add Database Primary Key to a Table", "Do Not Add"))))) + { + if (msgboxResult == KMessageBox::Cancel) + _ERR; //cancel accepting + + //add implicit PK field +//! @todo make this field hidden (what about e.g. pgsql?) + m_implicitPrimaryKeyAdded = true; + + QString fieldName("id"); + QString fieldCaption("Id"); + + QStringList colnames; + for (uint col = 0; col < numCols; col++) + colnames.append( m_table->text(0, col).lower().simplifyWhiteSpace() ); + + if (colnames.find(fieldName)!=colnames.end()) { + int num = 1; + while (colnames.find(fieldName+QString::number(num))!=colnames.end()) + num++; + fieldName += QString::number(num); + fieldCaption += QString::number(num); + } + KexiDB::Field *field = new KexiDB::Field( + fieldName, + KexiDB::Field::Integer, + KexiDB::Field::NoConstraints, + KexiDB::Field::NoOptions, + 0,0, //uint length=0, uint precision=0, + QVariant(), //QVariant defaultValue=QVariant(), + fieldCaption + ); //no description and width for now + field->setPrimaryKey(true); + field->setAutoIncrement(true); + m_destinationTableSchema->addField( field ); + } + + for (uint col = 0; col < numCols; col++) { + QString fieldCaption( m_table->text(0, col).simplifyWhiteSpace() ); + QString fieldName( KexiUtils::string2Identifier( fieldCaption ) ); + if (m_destinationTableSchema->field(fieldName)) { + QString fixedFieldName; + uint i = 2; //"apple 2, apple 3, etc. if there're many "apple" names + do { + fixedFieldName = fieldName + "_" + QString::number(i); + if (!m_destinationTableSchema->field(fixedFieldName)) + break; + i++; + } while (true); + fieldName = fixedFieldName; + fieldCaption += (" " + QString::number(i)); + } + const int detectedType = m_detectedTypes[col]; + KexiDB::Field::Type fieldType; + if (detectedType==_DATE_TYPE) + fieldType = KexiDB::Field::Date; + if (detectedType==_TIME_TYPE) + fieldType = KexiDB::Field::Time; + if (detectedType==_DATETIME_TYPE) + fieldType = KexiDB::Field::DateTime; + else if (detectedType==_NUMBER_TYPE) + fieldType = KexiDB::Field::Integer; + else if (detectedType==_FP_NUMBER_TYPE) + fieldType = KexiDB::Field::Double; +//! @todo what about time and float/double types and different integer subtypes? + else //_TEXT_TYPE and the rest + fieldType = KexiDB::Field::Text; +//! @todo what about long text? + + KexiDB::Field *field = new KexiDB::Field( + fieldName, + fieldType, + KexiDB::Field::NoConstraints, + KexiDB::Field::NoOptions, + 0,0, //uint length=0, uint precision=0, + QVariant(), //QVariant defaultValue=QVariant(), + fieldCaption + ); //no description and width for now + + if ((int)col == m_primaryKeyColumn) { + field->setPrimaryKey(true); + field->setAutoIncrement(true); + } + m_destinationTableSchema->addField( field ); + } + + KexiDB::Transaction transaction = m_conn->beginTransaction(); + if (transaction.isNull()) { + msg.showErrorMessage(m_conn); + _ERR; + } + KexiDB::TransactionGuard tg(transaction); + + //-create physical table + if (!m_conn->createTable(m_destinationTableSchema, allowOverwriting)) { + msg.showErrorMessage(m_conn); + _ERR; + } + +#define _DROP_DEST_TABLE_AND_RETURN \ + { \ + if (m_importingProgressDlg) \ + m_importingProgressDlg->hide(); \ + project->deleteUnstoredItem(partItemForSavedTable); \ + m_conn->dropTable(m_destinationTableSchema); /*alsoRemoveSchema*/ \ + m_destinationTableSchema = 0; \ + m_conn = 0; \ + return; \ + } + + m_importingStatement = m_conn->prepareStatement( + KexiDB::PreparedStatement::InsertStatement, *m_destinationTableSchema); + if (!m_importingStatement) { + msg.showErrorMessage(m_conn); + _DROP_DEST_TABLE_AND_RETURN; + } + + if (m_file) { + if (!m_importingProgressDlg) { + m_importingProgressDlg = new KProgressDialog( this, "m_importingProgressDlg", + i18n("Importing CSV Data"), QString::null, true ); + } + m_importingProgressDlg->setLabel( + i18n("Importing CSV Data from <nobr>\"%1\"</nobr> into \"%2\" table...") + .arg(QDir::convertSeparators(m_fname)).arg(m_destinationTableSchema->name()) ); + m_importingProgressDlg->progressBar()->setTotalSteps( QFileInfo(*m_file).size() ); + m_importingProgressDlg->show(); + } + + int row, column, maxColumn; + QString field = QString::null; + + // main job + res = loadRows(field, row, column, maxColumn, false /*!gui*/ ); + + delete m_importingProgressDlg; + m_importingProgressDlg = 0; + if (true != res) { + //importing cancelled or failed + if (!res) //do not display err msg when res == cancelled + msg.showErrorMessage(m_conn); + _DROP_DEST_TABLE_AND_RETURN; + } + + // file with only one line without '\n' + if (field.length() > 0) + { + setText(row - m_startline, column, field, false /*!gui*/); + //fill remaining empty fields (database wants them explicitly) + for (int additionalColumn = column; additionalColumn <= maxColumn; additionalColumn++) { + setText(row - m_startline, additionalColumn, QString::null, false /*!gui*/); + } + if (!saveRow(false /*!gui*/)) { + msg.showErrorMessage(m_conn); + _DROP_DEST_TABLE_AND_RETURN; + } + ++row; + field = QString::null; + } + + if (!tg.commit()) { + msg.showErrorMessage(m_conn); + _DROP_DEST_TABLE_AND_RETURN; + } + + //-now we can store the item + partItemForSavedTable->setIdentifier( m_destinationTableSchema->id() ); + project->addStoredItem( part->info(), partItemForSavedTable ); + + QDialog::accept(); + KMessageBox::information(this, i18n("Data has been successfully imported to table \"%1\".") + .arg(m_destinationTableSchema->name())); + parentWidget()->raise(); + m_conn = 0; +} + +int KexiCSVImportDialog::getHeader(int col) +{ + QString header = m_table->horizontalHeader()->label(col); + + if (header == i18n("Text type for column", "Text")) + return TEXT; + else if (header == i18n("Numeric type for column", "Number")) + return NUMBER; + else if (header == i18n("Currency type for column", "Currency")) + return CURRENCY; + else + return DATE; +} + +QString KexiCSVImportDialog::getText(int row, int col) +{ + return m_table->text(row, col); +} + +void KexiCSVImportDialog::ignoreDuplicatesChanged(int) +{ + fillTable(); +} + +void KexiCSVImportDialog::slot1stRowForFieldNamesChanged(int) +{ + m_adjustRows=true; + if (m_1stRowForFieldNames->isChecked() && m_startline>0 && m_startline>=(m_startAtLineSpinBox->maxValue()-1)) + m_startline--; + fillTable(); +} + +void KexiCSVImportDialog::optionsButtonClicked() +{ + KexiCSVImportOptionsDialog dlg(m_options, this); + if (QDialog::Accepted != dlg.exec()) + return; + + KexiCSVImportOptions newOptions( dlg.options() ); + if (m_options != newOptions) { + m_options = newOptions; + if (!openData()) + return; + fillTable(); + } +} + +bool KexiCSVImportDialog::eventFilter ( QObject * watched, QEvent * e ) +{ + QEvent::Type t = e->type(); + // temporary disable keyboard and mouse events for time-consuming tasks + if (m_blockUserEvents && (t==QEvent::KeyPress || t==QEvent::KeyRelease + || t==QEvent::MouseButtonPress || t==QEvent::MouseButtonDblClick + || t==QEvent::Paint )) + return true; + + if (watched == m_startAtLineSpinBox && t==QEvent::KeyPress) { + QKeyEvent *ke = static_cast<QKeyEvent*>(e); + if (ke->key()==Qt::Key_Enter || ke->key()==Qt::Key_Return) { + m_table->setFocus(); + return true; + } + } + return QDialog::eventFilter( watched, e ); +} + +void KexiCSVImportDialog::slotPrimaryKeyFieldToggled(bool on) +{ + Q_UNUSED(on); + formatChanged(_PK_FLAG); +} + +void KexiCSVImportDialog::updateRowCountInfo() +{ + m_infoLbl->setFileName( m_fname ); + if (m_allRowsLoadedInPreview) { + m_infoLbl->setCommentText( + i18n("row count", "(rows: %1)").arg( m_table->numRows()-1+m_startline ) ); + QToolTip::remove( m_infoLbl ); + } + else { + m_infoLbl->setCommentText( + i18n("row count", "(rows: more than %1)").arg( m_table->numRows()-1+m_startline ) ); + QToolTip::add( m_infoLbl->commentLabel(), i18n("Not all rows are visible on this preview") ); + } +} + +#include "kexicsvimportdialog.moc" diff --git a/kexi/plugins/importexport/csv/kexicsvimportdialog.h b/kexi/plugins/importexport/csv/kexicsvimportdialog.h new file mode 100644 index 00000000..1f7b159e --- /dev/null +++ b/kexi/plugins/importexport/csv/kexicsvimportdialog.h @@ -0,0 +1,231 @@ +/* This file is part of the KDE project + Copyright (C) 2005-2006 Jaroslaw Staniek <js@iidea.pl> + + This work is based on kspread/dialogs/kspread_dlg_csv.cc + and will be merged back with KOffice libraries. + + Copyright (C) 2002-2003 Norbert Andres <nandres@web.de> + Copyright (C) 2002-2003 Ariya Hidayat <ariya@kde.org> + Copyright (C) 2002 Laurent Montel <montel@kde.org> + Copyright (C) 1999 David Faure <faure@kde.org> + + 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 KEXI_CSVDIALOG_H +#define KEXI_CSVDIALOG_H + +#include <qvaluevector.h> +#include <qvaluelist.h> +#include <qptrvector.h> +#include <qregexp.h> +#include <qbitarray.h> + +#include <kdialogbase.h> + +#include <kexiutils/tristate.h> +#include <kexidb/connection.h> + +#include "kexicsvimportoptionsdlg.h" + +class QVBoxLayout; +class QHBoxLayout; +class QGridLayout; +class QButtonGroup; +class QCheckBox; +class QLabel; +class QLineEdit; +class QPushButton; +class QRadioButton; +class QTable; +class QFile; +class KComboBox; +class KIntSpinBox; +class KProgressDialog; + +class KexiMainWindow; +class KexiCSVDelimiterWidget; +class KexiCSVTextQuoteComboBox; +class KexiCSVInfoLabel; + +/** + * @short Kexi CSV import dialog + * + * This is temporary solution for Kexi CSV import, + * based on kspread/dialogs/kspread_dlg_csv.h, cc. + * + * Provides dialog for managing CSV (comma separated value) data. + * + * Currently KexiCSVImportDialog is used for converting text into columns, + * inserting text file and pasting text from clipboard, where conversion + * from CSV (comma separated value) data is is all required. + * The different purposed mentioned above is determined + * using mode, which can be Column, File, or Clipboard respectively. +*/ +class KexiCSVImportDialog : public KDialogBase +{ + Q_OBJECT + + public: + enum Mode { Clipboard, File /*, Column*/ }; + enum Header { TEXT, NUMBER, DATE, CURRENCY }; + + //! @todo what about making it kexidb-independent? + KexiCSVImportDialog( Mode mode, KexiMainWindow* mainWin, QWidget * parent, + const char * name = 0/*, QRect const & rect*/); + + virtual ~KexiCSVImportDialog(); + + bool cancelled() const; + virtual bool eventFilter ( QObject * watched, QEvent * e ); + + protected: + bool openData(); + virtual void accept(); + + private: + QGridLayout* MyDialogLayout; + QHBoxLayout* Layout1; + QTable* m_table; + KexiCSVDelimiterWidget* m_delimiterWidget; + bool m_detectDelimiter; //!< true if delimiter should be detected + //!< (true by default, set to false if user sets delimiter) + QString m_formatComboText; + QLabel* m_formatLabel; + KComboBox* m_formatCombo; + KIntSpinBox *m_startAtLineSpinBox; + KexiCSVTextQuoteComboBox* m_comboQuote; + QLabel* m_startAtLineLabel; + QLabel* TextLabel2; + QCheckBox* m_ignoreDuplicates; + QCheckBox* m_1stRowForFieldNames; + QCheckBox* m_primaryKeyField; + + KexiMainWindow* m_mainWin; + + void detectTypeAndUniqueness(int row, int col, const QString& text); + void setText(int row, int col, const QString& text, bool inGUI); + + /*! Parses date from \a text and stores into \a date. + m_dateRegExp is used for clever detection; + if '/' separated is found, it's assumed the format is american mm/dd/yyyy. + This function supports omitted zeros, so 1/2/2006 is parsed properly too. + \return true on success. */ + bool parseDate(const QString& text, QDate& date); + + /*! Parses time from \a text and stores into \a date. + m_timeRegExp1 and m_timeRegExp2 are used for clever detection; + both hh:mm:ss and hh:mm are supported. + This function supports omitted zeros, so 1:2:3 is parsed properly too. + \return true on success. */ + bool parseTime(const QString& text, QTime& time); + + /*! Called after the first fillTable() when number of rows is unknown. */ + void adjustRows(int iRows); + + int getHeader(int col); + QString getText(int row, int col); + void updateColumnText(int col); + void updateRowCountInfo(); + tristate loadRows(QString &field, int &row, int &columnm, int &maxColumn, bool inGUI); + + /*! Detects delimiter by looking at first 4K bytes of the data. Used by loadRows(). + The used algorithm: + 1. Look byte by byte and locate special characters that can be delimiters. + Special fact is taken into account: if there are '"' quotes used for text values, + delimiters that follow directly the closing quote has higher priority than the one + that follows other character. We do not assume that every text value is quoted. + Summing up, there is following hierarchy (from highest to lowest): + quote+tab, quote+semicolon, quote+comma, tab, semicolon, comma. + Space characters are skipped. Text inside quotes is skipped, as well as double + (escaped) quotes. + 2. While scanning the data, for every row following number of tabs, semicolons and commas + (only these outside of the quotes) are computed. On every line the values are appended + to a separate list (QValueList<int>). + 3. After scanning, all the values are checked on the QValueList<int> of tabs. + If the list has more one element (so there was more than one row) and all the values + (numbers of tabs) are equal, it's very probable the tab is a delimiter. + So, this character is returned as a delimiter. + 3a. The same algorithm as in 3. is performed for semicolon character. + 3b. The same algorithm as in 3. is performed for comma character. + 4. If the step 3. did not return a delimiter, a character found in step 1. with + the highest priority is retured as delimiter. */ + QString detectDelimiterByLookingAtFirstBytesOfFile(QTextStream& inputStream); + + /*! Callback, called whenever row is loaded in loadRows(). When inGUI is true, + nothing is performed, else database buffer is written back to the database. */ + bool saveRow(bool inGUI); + + bool m_cancelled; + bool m_adjustRows; + int m_startline; + QChar m_textquote; + QString m_clipboardData; + QByteArray m_fileArray; + Mode m_mode; + int m_prevSelectedCol; + + //! vector of detected types, 0==text (the default), 1==number, 2==date +//! @todo more types + QValueVector<int> m_detectedTypes; + + //! m_detectedUniqueColumns[i]==true means that i-th column has unique values + //! (only for numeric type) + QPtrVector< QValueList<int> > m_uniquenessTest; + + QRegExp m_dateRegExp, m_timeRegExp1, m_timeRegExp2, m_fpNumberRegExp; + QValueVector<QString> m_typeNames, m_columnNames; + QBitArray m_changedColumnNames; + bool m_columnsAdjusted : 1; //!< to call adjustColumn() only once + bool m_1stRowForFieldNamesDetected : 1; //!< used to force rerun fillTable() after 1st row + bool m_firstFillTableCall : 1; //!< used to know whether it's 1st fillTable() call + bool m_blockUserEvents : 1; + int m_primaryKeyColumn; //!< index of column with PK assigned (-1 if none) + int m_maximumRowsForPreview; + int m_maximumBytesForPreview; + QPixmap m_pkIcon; + QString m_fname; + QFile* m_file; + QTextStream *m_inputStream; //!< used in loadData() + KexiCSVImportOptions m_options; + KProgressDialog *m_loadingProgressDlg, *m_importingProgressDlg; + bool m_dialogCancelled; + KexiCSVInfoLabel *m_infoLbl; + KexiDB::Connection *m_conn; //!< (temp) database connection used for importing + KexiDB::TableSchema *m_destinationTableSchema; //!< (temp) dest. table schema used for importing + KexiDB::PreparedStatement::Ptr m_importingStatement; + QValueList<QVariant> m_dbRowBuffer; //!< (temp) used for importing + bool m_implicitPrimaryKeyAdded; //!< (temp) used for importing + bool m_allRowsLoadedInPreview; //!< we need to know whether all rows were loaded or it's just a partial data preview + bool m_stoppedAt_MAX_BYTES_TO_PREVIEW; //!< used to compute m_allRowsLoadedInPreview + + private slots: + void fillTable(); + void fillTableLater(); + void initLater(); + void formatChanged(int id); + void delimiterChanged(const QString& delimiter); + void startlineSelected(int line); + void textquoteSelected(int); + void currentCellChanged(int, int col); + void ignoreDuplicatesChanged(int); + void slot1stRowForFieldNamesChanged(int); + void cellValueChanged(int row,int col); + void optionsButtonClicked(); + void slotPrimaryKeyFieldToggled(bool on); +}; + +#endif diff --git a/kexi/plugins/importexport/csv/kexicsvimportoptionsdlg.cpp b/kexi/plugins/importexport/csv/kexicsvimportoptionsdlg.cpp new file mode 100644 index 00000000..b381dde3 --- /dev/null +++ b/kexi/plugins/importexport/csv/kexicsvimportoptionsdlg.cpp @@ -0,0 +1,140 @@ +/* This file is part of the KDE project + Copyright (C) 2005-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 "kexicsvimportoptionsdlg.h" +#include <widget/kexicharencodingcombobox.h> + +#include <qlabel.h> +#include <qlayout.h> +#include <qtextcodec.h> +#include <qcheckbox.h> + +#include <kapplication.h> +#include <kconfig.h> +#include <kcombobox.h> +#include <klocale.h> +#include <kglobal.h> +#include <kcharsets.h> + +KexiCSVImportOptions::KexiCSVImportOptions() +{ + kapp->config()->setGroup("ImportExport"); + encoding = kapp->config()->readEntry("DefaultEncodingForImportingCSVFiles"); + if (encoding.isEmpty()) { + encoding = QString::fromLatin1(KGlobal::locale()->encoding()); + defaultEncodingExplicitySet = false; + } + else + defaultEncodingExplicitySet = true; + + stripWhiteSpaceInTextValuesChecked + = kapp->config()->readBoolEntry("StripBlanksOffOfTextValuesWhenImportingCSVFiles", true); +} + +KexiCSVImportOptions::~KexiCSVImportOptions() +{ +} + +bool KexiCSVImportOptions::operator== ( const KexiCSVImportOptions & opt ) const +{ + return defaultEncodingExplicitySet==opt.defaultEncodingExplicitySet + && stripWhiteSpaceInTextValuesChecked==opt.stripWhiteSpaceInTextValuesChecked + && encoding==opt.encoding; +} + +bool KexiCSVImportOptions::operator!= ( const KexiCSVImportOptions & opt ) const +{ + return !( *this==opt ); +} + +//---------------------------------- + +KexiCSVImportOptionsDialog::KexiCSVImportOptionsDialog( + const KexiCSVImportOptions& options, QWidget* parent ) + : KDialogBase( + KDialogBase::Plain, + i18n( "CSV Import Options" ), + Ok|Cancel, + Ok, + parent, + "KexiCSVImportOptionsDialog", + true, + false + ) +{ + QGridLayout *lyr = new QGridLayout( plainPage(), 5, 3, + KDialogBase::marginHint(), KDialogBase::spacingHint()); + + m_encodingComboBox = new KexiCharacterEncodingComboBox(plainPage(), options.encoding); + lyr->addWidget( m_encodingComboBox, 0, 1 ); + + QLabel* lbl = new QLabel( m_encodingComboBox, i18n("Text encoding:"), plainPage()); + lyr->addWidget( lbl, 0, 0 ); + + lyr->addItem( new QSpacerItem( 20, KDialogBase::spacingHint(), QSizePolicy::Fixed, QSizePolicy::Fixed ), 2, 1 ); + lyr->addItem( new QSpacerItem( 121, KDialogBase::spacingHint(), QSizePolicy::Expanding, QSizePolicy::Minimum ), 0, 2 ); + + m_chkAlwaysUseThisEncoding = new QCheckBox( + i18n("Always use this encoding when importing CSV data files"), plainPage()); + lyr->addWidget( m_chkAlwaysUseThisEncoding, 1, 1 ); + + m_chkStripWhiteSpaceInTextValues = new QCheckBox( + i18n("Strip leading and trailing blanks off of text values"), plainPage()); + lyr->addWidget( m_chkStripWhiteSpaceInTextValues, 3, 1 ); + lyr->addItem( new QSpacerItem( 20, KDialogBase::spacingHint(), QSizePolicy::Minimum, QSizePolicy::Expanding ), 4, 1 ); + + //update widgets + if (options.defaultEncodingExplicitySet) { + m_encodingComboBox->setSelectedEncoding(options.encoding); + m_chkAlwaysUseThisEncoding->setChecked(true); + } + m_chkStripWhiteSpaceInTextValues->setChecked(options.stripWhiteSpaceInTextValuesChecked); + + adjustSize(); + m_encodingComboBox->setFocus(); +} + +KexiCSVImportOptionsDialog::~KexiCSVImportOptionsDialog() +{ +} + +KexiCSVImportOptions KexiCSVImportOptionsDialog::options() const +{ + KexiCSVImportOptions opt; + opt.encoding = m_encodingComboBox->selectedEncoding(); + opt.stripWhiteSpaceInTextValuesChecked = m_chkStripWhiteSpaceInTextValues->isChecked(); + return opt; +} + +void KexiCSVImportOptionsDialog::accept() +{ + kapp->config()->setGroup("ImportExport"); + if (m_chkAlwaysUseThisEncoding->isChecked()) + kapp->config()->writeEntry("DefaultEncodingForImportingCSVFiles", + m_encodingComboBox->selectedEncoding()); + else + kapp->config()->deleteEntry("DefaultEncodingForImportingCSVFiles"); + + kapp->config()->writeEntry("StripBlanksOffOfTextValuesWhenImportingCSVFiles", + m_chkStripWhiteSpaceInTextValues->isChecked()); + + KDialogBase::accept(); +} + +#include "kexicsvimportoptionsdlg.moc" diff --git a/kexi/plugins/importexport/csv/kexicsvimportoptionsdlg.h b/kexi/plugins/importexport/csv/kexicsvimportoptionsdlg.h new file mode 100644 index 00000000..e0567c9c --- /dev/null +++ b/kexi/plugins/importexport/csv/kexicsvimportoptionsdlg.h @@ -0,0 +1,62 @@ +/* This file is part of the KDE project + Copyright (C) 2005 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 KEXICSVOPTIONSDIALOG_H +#define KEXICSVOPTIONSDIALOG_H + +#include <kdialogbase.h> +#include <qcheckbox.h> + +class KexiCharacterEncodingComboBox; + +//! @short CSV Options +class KexiCSVImportOptions +{ + public: + KexiCSVImportOptions(); + ~KexiCSVImportOptions(); + + bool operator== ( const KexiCSVImportOptions & opt ) const; + bool operator!= ( const KexiCSVImportOptions & opt ) const; + + QString encoding; + bool defaultEncodingExplicitySet; + bool stripWhiteSpaceInTextValuesChecked; +}; + +//! @short CSV Options dialog +class KexiCSVImportOptionsDialog : public KDialogBase +{ + Q_OBJECT + public: + KexiCSVImportOptionsDialog( const KexiCSVImportOptions& options, QWidget* parent = 0 ); + virtual ~KexiCSVImportOptionsDialog(); + + KexiCSVImportOptions options() const; + + protected slots: + virtual void accept(); + + protected: + KexiCharacterEncodingComboBox *m_encodingComboBox; + QCheckBox *m_chkAlwaysUseThisEncoding; + QCheckBox *m_chkStripWhiteSpaceInTextValues; +}; + +#endif diff --git a/kexi/plugins/importexport/csv/kexicsvwidgets.cpp b/kexi/plugins/importexport/csv/kexicsvwidgets.cpp new file mode 100644 index 00000000..8e3cf4c2 --- /dev/null +++ b/kexi/plugins/importexport/csv/kexicsvwidgets.cpp @@ -0,0 +1,233 @@ +/* This file is part of the KDE project + Copyright (C) 2005 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 "kexicsvwidgets.h" + +#include <qdir.h> +#include <qlabel.h> +#include <qlayout.h> + +#include <klocale.h> +#include <klineedit.h> +#include <kdialogbase.h> +#include <kactivelabel.h> +#include <kiconloader.h> +#include <kmimetype.h> + +#define KEXICSV_OTHER_DELIMITER_INDEX 4 + +KexiCSVDelimiterWidget::KexiCSVDelimiterWidget( bool lineEditOnBottom, QWidget * parent ) + : QWidget(parent, "KexiCSVDelimiterWidget") + , m_availableDelimiters(KEXICSV_OTHER_DELIMITER_INDEX) + +{ + QBoxLayout *lyr = + lineEditOnBottom ? + (QBoxLayout *)new QVBoxLayout( this, 0, KDialogBase::spacingHint() ) + : (QBoxLayout *)new QHBoxLayout( this, 0, KDialogBase::spacingHint() ); + + m_availableDelimiters[0]=KEXICSV_DEFAULT_FILE_DELIMITER; + m_availableDelimiters[1]=";"; + m_availableDelimiters[2]="\t"; + m_availableDelimiters[3]=" "; + + m_combo = new KComboBox(this, "KexiCSVDelimiterComboBox"); + m_combo->insertItem( i18n("Comma \",\"") ); //<-- KEXICSV_DEFAULT_FILE_DELIMITER + m_combo->insertItem( i18n( "Semicolon \";\"" ) ); + m_combo->insertItem( i18n( "Tabulator" ) ); + m_combo->insertItem( i18n( "Space \" \"" ) ); + m_combo->insertItem( i18n( "Other" ) ); + lyr->addWidget(m_combo); + setFocusProxy(m_combo); + + m_delimiterEdit = new KLineEdit( this, "m_delimiterEdit" ); +// m_delimiterEdit->setSizePolicy( QSizePolicy( (QSizePolicy::SizeType)0, (QSizePolicy::SizeType)0, 0, 0, m_delimiterEdit->sizePolicy().hasHeightForWidth() ) ); + m_delimiterEdit->setMaximumSize( QSize( 30, 32767 ) ); + m_delimiterEdit->setMaxLength(1); + lyr->addWidget( m_delimiterEdit ); + if (!lineEditOnBottom) + lyr->addStretch(2); + + slotDelimiterChangedInternal(KEXICSV_DEFAULT_FILE_DELIMITER_INDEX); //this will init m_delimiter + connect(m_combo, SIGNAL(activated(int)), + this, SLOT(slotDelimiterChanged(int))); + connect(m_delimiterEdit, SIGNAL(returnPressed()), + this, SLOT(slotDelimiterLineEditReturnPressed())); + connect(m_delimiterEdit, SIGNAL(textChanged( const QString & )), + this, SLOT(slotDelimiterLineEditTextChanged( const QString & ) )); +} + +void KexiCSVDelimiterWidget::slotDelimiterChanged(int index) +{ + slotDelimiterChangedInternal(index); + if (index==KEXICSV_OTHER_DELIMITER_INDEX) + m_delimiterEdit->setFocus(); +} + +void KexiCSVDelimiterWidget::slotDelimiterChangedInternal(int index) +{ + bool changed = false; + if (index > KEXICSV_OTHER_DELIMITER_INDEX) + return; + else if (index == KEXICSV_OTHER_DELIMITER_INDEX) { + changed = m_delimiter != m_delimiterEdit->text(); + m_delimiter = m_delimiterEdit->text(); + } + else { + changed = m_delimiter != m_availableDelimiters[index]; + m_delimiter = m_availableDelimiters[index]; + } + m_delimiterEdit->setEnabled(index == KEXICSV_OTHER_DELIMITER_INDEX); + if (changed) + emit delimiterChanged(m_delimiter); +} + +void KexiCSVDelimiterWidget::slotDelimiterLineEditReturnPressed() +{ + if (m_combo->currentItem() != KEXICSV_OTHER_DELIMITER_INDEX) + return; + slotDelimiterChangedInternal(KEXICSV_OTHER_DELIMITER_INDEX); +} + +void KexiCSVDelimiterWidget::slotDelimiterLineEditTextChanged( const QString & ) +{ + slotDelimiterChangedInternal(KEXICSV_OTHER_DELIMITER_INDEX); +} + +void KexiCSVDelimiterWidget::setDelimiter(const QString& delimiter) +{ + QValueVector<QString>::ConstIterator it = m_availableDelimiters.constBegin(); + int index = 0; + for (; it != m_availableDelimiters.constEnd(); ++it, index++) { + if (*it == delimiter) { + m_combo->setCurrentItem(index); + slotDelimiterChangedInternal(index); + return; + } + } + //else: set other (custom) delimiter + m_delimiterEdit->setText(delimiter); + m_combo->setCurrentItem(KEXICSV_OTHER_DELIMITER_INDEX); + slotDelimiterChangedInternal(KEXICSV_OTHER_DELIMITER_INDEX); +} + +//---------------------------------------------------- + +KexiCSVTextQuoteComboBox::KexiCSVTextQuoteComboBox( QWidget * parent ) + : KComboBox(parent, "KexiCSVTextQuoteComboBox") +{ + insertItem( "\"" ); + insertItem( "'" ); + insertItem( i18n( "None" ) ); +} + +QString KexiCSVTextQuoteComboBox::textQuote() const +{ + if (currentItem()==2) + return QString::null; + return currentText(); +} + +void KexiCSVTextQuoteComboBox::setTextQuote(const QString& textQuote) +{ + if (textQuote=="\"" || textQuote=="'") + setCurrentText(textQuote); + else if (textQuote.isEmpty()) + setCurrentText(i18n( "None" )); +} + +//---------------------------------------------------- + +KexiCSVInfoLabel::KexiCSVInfoLabel( const QString& labelText, QWidget* parent ) + : QWidget(parent, "KexiCSVInfoLabel") +{ + QVBoxLayout *vbox = new QVBoxLayout( this, 0, KDialogBase::spacingHint() ); + QHBoxLayout *hbox = new QHBoxLayout( this ); + vbox->addLayout(hbox); + m_leftLabel = new QLabel(labelText, this); + m_leftLabel->setMinimumWidth(130); + m_leftLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); + m_leftLabel->setAlignment(Qt::AlignVCenter | Qt::AlignLeft | Qt::WordBreak); + hbox->addWidget(m_leftLabel); + m_iconLbl = new QLabel(this); + m_iconLbl->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); + m_iconLbl->setAlignment(Qt::AlignVCenter | Qt::AlignLeft); + m_fnameLbl = new KActiveLabel(this); + m_fnameLbl->setFocusPolicy(NoFocus); + m_fnameLbl->setTextFormat(Qt::PlainText); + m_fnameLbl->setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding,1,0)); + m_fnameLbl->setLineWidth(1); + m_fnameLbl->setFrameStyle(QFrame::Box); + m_fnameLbl->setAlignment(Qt::AlignVCenter | Qt::AlignLeft | Qt::WordBreak); + hbox->addSpacing(5); + hbox->addWidget(m_iconLbl); + hbox->addWidget(m_fnameLbl, 1, Qt::AlignVCenter | Qt::AlignLeft | Qt::WordBreak); + hbox->addSpacing(10); + m_commentLbl = new KActiveLabel(this); + m_commentLbl->setFocusPolicy(NoFocus); + m_commentLbl->setTextFormat(Qt::PlainText); + m_commentLbl->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); + m_commentLbl->setLineWidth(1); + m_commentLbl->setFrameStyle(QFrame::Box); + m_commentLbl->setAlignment(Qt::AlignVCenter | Qt::AlignLeft | Qt::WordBreak); + hbox->addWidget(m_commentLbl, 0, Qt::AlignVCenter | Qt::AlignRight | Qt::WordBreak); + + m_separator = new QFrame(this); + m_separator->setFrameShape(QFrame::HLine); + m_separator->setFrameShadow(QFrame::Sunken); + vbox->addWidget(m_separator); +} + +void KexiCSVInfoLabel::setFileName( const QString& fileName ) +{ + m_fnameLbl->setText( QDir::convertSeparators(fileName) ); + if (!fileName.isEmpty()) { + m_iconLbl->setPixmap( + KMimeType::pixmapForURL(KURL::fromPathOrURL(fileName), 0, KIcon::Desktop) ); + } +} + +void KexiCSVInfoLabel::setLabelText( const QString& text ) +{ + m_fnameLbl->setText( text ); +// int lines = m_fnameLbl->lines(); +// m_fnameLbl->setFixedHeight( +// QFontMetrics(m_fnameLbl->currentFont()).height() * lines ); +} + +void KexiCSVInfoLabel::setIcon(const QString& iconName) +{ + m_iconLbl->setPixmap( DesktopIcon(iconName) ); +} + +void KexiCSVInfoLabel::setCommentText( const QString& text ) +{ + m_commentLbl->setText(text); +} + +//---------------------------------------------------- + +QStringList csvMimeTypes() +{ + QStringList mimetypes; + mimetypes << "text/x-csv" << "text/plain" << "all/allfiles"; + return mimetypes; +} + +#include "kexicsvwidgets.moc" diff --git a/kexi/plugins/importexport/csv/kexicsvwidgets.h b/kexi/plugins/importexport/csv/kexicsvwidgets.h new file mode 100644 index 00000000..f128b658 --- /dev/null +++ b/kexi/plugins/importexport/csv/kexicsvwidgets.h @@ -0,0 +1,116 @@ +/* This file is part of the KDE project + Copyright (C) 2005 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 KEXI_CSVWIDGETS_H +#define KEXI_CSVWIDGETS_H + +#include <qvaluevector.h> +#include <kcombobox.h> + +class KLineEdit; +class KActiveLabel; +class QLabel; + +#define KEXICSV_DEFAULT_FILE_TEXT_QUOTE "\"" +#define KEXICSV_DEFAULT_CLIPBOARD_TEXT_QUOTE "" +#define KEXICSV_DEFAULT_FILE_DELIMITER "," +#define KEXICSV_DEFAULT_CLIPBOARD_DELIMITER "\t" +#define KEXICSV_DEFAULT_FILE_DELIMITER_INDEX 0 + +//! \return a list of mimetypes usable for handling CSV format handling +QStringList csvMimeTypes(); + +/*! @short A helper widget showing a short text information with an icon. + See ctor for details. + Used by CSV import and export dialogs. */ +class KexiCSVInfoLabel : public QWidget +{ + public: + /* Sets up a new info label \a labelText label with text like "Preview of data from file:". + setFileName() can be used to display filename and setCommentAfterFileName() to display + additional comment. + + The widget's layout can look like this: + + \pre [icon] [labeltext] [filename] [comment] + */ + KexiCSVInfoLabel( const QString& labelText, QWidget* parent ); + + void setFileName( const QString& fileName ); + void setLabelText( const QString& text ); + void setCommentText( const QString& text ); +// void setIconForFileName(); + + //! sets icon pixmap to \a iconName. Used wher setIconForFilename was false in ctor. + void setIcon(const QString& iconName); + + QLabel* leftLabel() const { return m_leftLabel; } + KActiveLabel* fileNameLabel() const { return m_fnameLbl; } + KActiveLabel* commentLabel() const { return m_commentLbl; } + QFrame* separator() const { return m_separator; } + + protected: + QLabel *m_leftLabel, *m_iconLbl; + KActiveLabel *m_fnameLbl, *m_commentLbl; + QFrame* m_separator; +}; + +//! @short A combo box widget providing a list of possible delimiters +//! Used by CSV import and export dialogs +class KexiCSVDelimiterWidget : public QWidget +{ + Q_OBJECT + + public: + KexiCSVDelimiterWidget( bool lineEditOnBottom, QWidget * parent = 0 ); + + QString delimiter() const { return m_delimiter; } + void setDelimiter(const QString& delimiter); + + signals: + void delimiterChanged(const QString& delimiter); + + protected slots: + //! only called when a delimiter was set by user directly + void slotDelimiterChanged(int idx); + void slotDelimiterChangedInternal(int idx); + void slotDelimiterLineEditTextChanged( const QString & ); + void slotDelimiterLineEditReturnPressed(); + + protected: + QString m_delimiter; + QValueVector<QString> m_availableDelimiters; + KComboBox* m_combo; + KLineEdit* m_delimiterEdit; +}; + +//! @short A combo box widget providing a list of possible quote characters +//! Used by CSV import and export dialogs +class KexiCSVTextQuoteComboBox : public KComboBox +{ + public: + KexiCSVTextQuoteComboBox( QWidget * parent = 0 ); + + QString textQuote() const; + + //! Sets text quote. Only available are: ", ', and empty string. + void setTextQuote(const QString& textQuote); +}; + +#endif diff --git a/kexi/plugins/macros/Makefile.am b/kexi/plugins/macros/Makefile.am new file mode 100644 index 00000000..cf5fb0d4 --- /dev/null +++ b/kexi/plugins/macros/Makefile.am @@ -0,0 +1,9 @@ +if include_kunittest + + # Unittest is disabled per default. + # TESTSDIR = tests + +endif + +METASOURCES = AUTO +SUBDIRS = lib kexiactions kexipart diff --git a/kexi/plugins/macros/configure.in.in b/kexi/plugins/macros/configure.in.in new file mode 100644 index 00000000..52fb9f5a --- /dev/null +++ b/kexi/plugins/macros/configure.in.in @@ -0,0 +1,13 @@ +# Check for kunittest +AC_MSG_CHECKING([for kunittest]) + +# First we check if the console unittester could be compiled +have_kunittest_header="no" +KDE_CHECK_HEADER(kunittest/tester.h, have_kunittest_header="yes", , ) +AM_CONDITIONAL(include_kunittest, test "$have_kunittest_header" = "yes") + +# Second we check if the GUI-unittester could be compiled +have_kunittestgui_header="no" +KDE_CHECK_HEADER(kunittest/runnergui.h, have_kunittestgui_header="yes", , ) +AM_CONDITIONAL(include_kunittestgui, test "$have_kunittestgui_header" = "yes") + diff --git a/kexi/plugins/macros/kexiactions/Makefile.am b/kexi/plugins/macros/kexiactions/Makefile.am new file mode 100644 index 00000000..4f42e5e9 --- /dev/null +++ b/kexi/plugins/macros/kexiactions/Makefile.am @@ -0,0 +1,27 @@ +include $(top_srcdir)/kexi/Makefile.global + +noinst_LTLIBRARIES = libkeximacroactions.la + +libkeximacroactions_la_SOURCES = \ + kexiaction.cpp \ + openaction.cpp \ + executeaction.cpp \ + navigateaction.cpp \ + messageaction.cpp \ + datatableaction.cpp + +libkeximacroactions_la_CXXFLAGS = $(USE_EXCEPTIONS) + +libkeximacroactions_la_LDFLAGS = $(all_libraries) +libkeximacroactions_la_LIBADD = \ + $(top_builddir)/kexi/plugins/macros/lib/libkomacro.la \ + $(top_builddir)/kexi/core/libkexicore.la \ + $(LIB_QT) $(LIB_KDECORE) $(LIB_KDEUI) + +libkeximacroactions_la_METASOURCES = AUTO +SUBDIRS = . + +INCLUDES = \ + -I$(top_srcdir)/kexi/core \ + -I$(top_srcdir)/kexi \ + $(all_includes) diff --git a/kexi/plugins/macros/kexiactions/datatableaction.cpp b/kexi/plugins/macros/kexiactions/datatableaction.cpp new file mode 100644 index 00000000..90b13e4f --- /dev/null +++ b/kexi/plugins/macros/kexiactions/datatableaction.cpp @@ -0,0 +1,185 @@ +/*************************************************************************** + * This file is part of the KDE project + * copyright (C) 2006 by Sebastian Sauer (mail@dipe.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 "datatableaction.h" +//#include "objectvariable.h" + +#include <core/kexi.h> +#include <core/kexiproject.h> +#include <core/kexipartmanager.h> +#include <core/kexipartinfo.h> +#include <core/kexipart.h> +#include <core/keximainwindow.h> +#include <core/kexiinternalpart.h> + +#include <klocale.h> + +using namespace KexiMacro; + +namespace KexiMacro { + + //static const QString OBJECT = "method"; + //static const QString OBJECT = "type"; + //static const QString OBJECT = "partitem"; + + template<class ACTIONIMPL> + class MethodVariable : public KexiVariable<ACTIONIMPL> + { + public: + MethodVariable(ACTIONIMPL* actionimpl) + : KexiVariable<ACTIONIMPL>(actionimpl, "method", i18n("Method")) + { + QStringList list; + list << "import" << "export"; + this->appendChild( KSharedPtr<KoMacro::Variable>( new KoMacro::Variable(list, "@list") ) ); + + this->setVariant( list[0] ); + } + }; + + template<class ACTIONIMPL> + class TypeVariable : public KexiVariable<ACTIONIMPL> + { + public: + TypeVariable(ACTIONIMPL* actionimpl) + : KexiVariable<ACTIONIMPL>(actionimpl, "type", i18n("Type")) + { + QStringList list; + list << "file" << "clipboard"; + this->appendChild( KSharedPtr<KoMacro::Variable>( new KoMacro::Variable(list, "@list") ) ); + + this->setVariant( list[0] ); + } + }; + + template<class ACTIONIMPL> + class PartItemVariable : public KexiVariable<ACTIONIMPL> + { + public: + PartItemVariable(ACTIONIMPL* actionimpl, const QString& partitem = QString::null) + : KexiVariable<ACTIONIMPL>(actionimpl, "partitem", i18n("Item")) + { + QStringList namelist; + if(actionimpl->mainWin()->project()) { + KexiPart::PartInfoList* parts = Kexi::partManager().partInfoList(); + for(KexiPart::PartInfoListIterator it(*parts); it.current(); ++it) { + KexiPart::Info* info = it.current(); + if(! info->isDataExportSupported()) + continue; + KexiPart::ItemDict* items = actionimpl->mainWin()->project()->items(info); + if(items) + for(KexiPart::ItemDictIterator item_it = *items; item_it.current(); ++item_it) + namelist << info->objectName() + "." + item_it.current()->name(); + } + for(QStringList::Iterator it = namelist.begin(); it != namelist.end(); ++it) + this->appendChild( KSharedPtr<KoMacro::Variable>(new KoMacro::Variable(*it)) ); + + //const QString name = info->objectName(); //info->groupName(); + //this->appendChild( KSharedPtr<KoMacro::Variable>(new KoMacro::Variable(name)) ); + } + const QString n = + namelist.contains(partitem) + ? partitem + : namelist.count() > 0 ? namelist[0] : ""; + this->setVariant(n); + kdDebug()<<"##################### KexiActions::ObjectVariable() variant="<<this->variant()<<endl; + } + }; + +} + +DataTableAction::DataTableAction() + : KexiAction("datatable", i18n("Data Table")) +{ + setVariable(KSharedPtr<KoMacro::Variable>( new MethodVariable<DataTableAction>(this) )); + setVariable(KSharedPtr<KoMacro::Variable>( new TypeVariable<DataTableAction>(this) )); + setVariable(KSharedPtr<KoMacro::Variable>( new PartItemVariable<DataTableAction>(this) )); +} + +DataTableAction::~DataTableAction() +{ +} + +bool DataTableAction::notifyUpdated(KSharedPtr<KoMacro::MacroItem> macroitem, const QString& name) +{ + kdDebug()<<"DataTableAction::notifyUpdated() name="<<name<<" macroitem.action="<<(macroitem->action() ? macroitem->action()->name() : "NOACTION")<<endl; + /* + KSharedPtr<KoMacro::Variable> variable = macroitem->variable(name, false); + if(! variable) { + kdWarning()<<"DataTableAction::notifyUpdated() No such variable="<<name<<" in macroitem."<<endl; + return false; + } + variable->clearChildren(); + if(name == "method") { + const int partitem = macroitem->variant(OBJECT, true).toString(); + macroitem->variable(OBJECT, true)->setChildren( + KoMacro::Variable::List() << KSharedPtr<KoMacro::Variable>(new ObjectVariable<ExecuteAction>(this, partitem)) ); + } + */ + return true; +} + +void DataTableAction::activate(KSharedPtr<KoMacro::Context> context) +{ + if(! mainWin()->project()) { + kdWarning() << "ExecuteAction::activate(KSharedPtr<KoMacro::Context>) Invalid project" << endl; + return; + } + + const QString method = context->variable("method")->variant().toString(); + const QString type = context->variable("type")->variant().toString(); + + const QString partitem = context->variable("partitem")->variant().toString(); + QString identifier; + if(! partitem.isEmpty()) { + QStringList parts = QStringList::split(".", partitem); + KexiPart::Part* part = Kexi::partManager().partForMimeType( QString("kexi/%1").arg(parts[0]) ); + KexiPart::Item* item = part ? mainWin()->project()->item(part->info(), parts[1]) : 0; + if(! item) + throw KoMacro::Exception(i18n("No such item \"%1\"").arg(partitem)); + identifier = QString::number(item->identifier()); + } + + QMap<QString,QString> args; + if(! identifier.isNull()) + args.insert("itemId", identifier); + + if(method == "import") { + args.insert("sourceType", type); + QDialog *dlg = KexiInternalPart::createModalDialogInstance( + "csv_importexport", "KexiCSVImportDialog", 0, mainWin(), 0, &args); + if (!dlg) + return; //error msg has been shown by KexiInternalPart + dlg->exec(); + delete dlg; + } + else if(method == "export") { + args.insert("destinationType", type); + QDialog *dlg = KexiInternalPart::createModalDialogInstance( + "csv_importexport", "KexiCSVExportWizard", 0, mainWin(), 0, &args); + if (!dlg) + return; //error msg has been shown by KexiInternalPart + dlg->exec(); + delete dlg; + } + else { + throw KoMacro::Exception(i18n("No such method \"%1\"").arg(method)); + } +} + +//#include "executeaction.moc" diff --git a/kexi/plugins/macros/kexiactions/datatableaction.h b/kexi/plugins/macros/kexiactions/datatableaction.h new file mode 100644 index 00000000..3b5b32c0 --- /dev/null +++ b/kexi/plugins/macros/kexiactions/datatableaction.h @@ -0,0 +1,76 @@ +/*************************************************************************** + * This file is part of the KDE project + * copyright (C) 2006 by Sebastian Sauer (mail@dipe.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 KEXIMACRO_DATATABLEACTION_H +#define KEXIMACRO_DATATABLEACTION_H + +#include "kexiaction.h" + +class KexiMainWindow; + +namespace KoMacro { + class Context; +} + +namespace KexiMacro { + + /** + * The DataTableAction class implements a @a KoMacro::Action + * to provide functionality to import or export a datatable. + * The datatable is used to deal with comma separated values. + */ + class DataTableAction : public KexiAction + { + Q_OBJECT + public: + + /** + * Constructor. + */ + DataTableAction(); + + /** + * Destructor. + */ + virtual ~DataTableAction(); + + /** + * This function is called, when the @a KoMacro::Variable + * with name @p name used within the @a KoMacro::MacroItem + * @p macroitem got changed. + * + * @param macroitem The @a KoMacro::MacroItem instance where + * the variable defined with @p name is located in. + * @param name The name the @a KoMacro::Variable has. + * @return true if the update was successfully else false + * is returned. + */ + virtual bool notifyUpdated(KSharedPtr<KoMacro::MacroItem> macroitem, const QString& name); + + public slots: + + /** + * Called if the @a Action should be executed within the + * defined @p context . + */ + virtual void activate(KSharedPtr<KoMacro::Context> context); + + }; +} + +#endif diff --git a/kexi/plugins/macros/kexiactions/executeaction.cpp b/kexi/plugins/macros/kexiactions/executeaction.cpp new file mode 100644 index 00000000..1e7f24a2 --- /dev/null +++ b/kexi/plugins/macros/kexiactions/executeaction.cpp @@ -0,0 +1,96 @@ +/*************************************************************************** + * This file is part of the KDE project + * copyright (C) 2006 by Sebastian Sauer (mail@dipe.org) + * copyright (C) 2006 by Bernd Steindorff (bernd@itii.de) + * copyright (C) 2006 by Sascha Kupper (kusato@kfnv.de) + * + * 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 "executeaction.h" + +#include <core/kexi.h> +#include <core/kexiproject.h> +#include <core/kexipartmanager.h> +#include <core/kexipartinfo.h> +#include <core/kexipart.h> +#include <core/keximainwindow.h> + +#include <klocale.h> + +using namespace KexiMacro; + +namespace KexiMacro { + static const QString OBJECT = "object"; + static const QString NAME = "name"; +} + +ExecuteAction::ExecuteAction() + : KexiAction("execute", i18n("Execute")) +{ + int conditions = ObjectVariable<ExecuteAction>::VisibleInNav | ObjectVariable<ExecuteAction>::Executable; + KSharedPtr<KoMacro::Variable> objvar = new ObjectVariable<ExecuteAction>(this, conditions); + setVariable(objvar); + + setVariable(KSharedPtr<KoMacro::Variable>( new ObjectNameVariable<ExecuteAction>(this, objvar->variant().toString()) )); +} + +ExecuteAction::~ExecuteAction() +{ +} + +bool ExecuteAction::notifyUpdated(KSharedPtr<KoMacro::MacroItem> macroitem, const QString& name) +{ + kdDebug()<<"ExecuteAction::notifyUpdated() name="<<name<<" macroitem.action="<<(macroitem->action() ? macroitem->action()->name() : "NOACTION")<<endl; + KSharedPtr<KoMacro::Variable> variable = macroitem->variable(name, false); + if(! variable) { + kdWarning()<<"ExecuteAction::notifyUpdated() No such variable="<<name<<" in macroitem."<<endl; + return false; + } + + variable->clearChildren(); + if(name == OBJECT) { + const QString objectvalue = macroitem->variant(OBJECT, true).toString(); // e.g. "macro" or "script" + const QString objectname = macroitem->variant(NAME, true).toString(); // e.g. "macro1" or "macro2" if objectvalue above is "macro" + macroitem->variable(NAME, true)->setChildren( + KoMacro::Variable::List() << KSharedPtr<KoMacro::Variable>(new ObjectNameVariable<ExecuteAction>(this, objectvalue, objectname)) ); + } + + return true; +} + +void ExecuteAction::activate(KSharedPtr<KoMacro::Context> context) +{ + if(! mainWin()->project()) { + kdWarning() << "ExecuteAction::activate(KSharedPtr<KoMacro::Context>) Invalid project" << endl; + return; + } + + const QString mimetype = QString("kexi/%1").arg( context->variable("object")->variant().toString() ); + const QString name = context->variable("name")->variant().toString(); + + KexiPart::Part* part = Kexi::partManager().partForMimeType(mimetype); + if(! part) { + throw KoMacro::Exception(i18n("No such mimetype \"%1\"").arg(mimetype)); + } + + KexiPart::Item* item = mainWin()->project()->item(part->info(), name); + if(! item) { + throw KoMacro::Exception(i18n("Failed to open part \"%1\" for mimetype \"%2\"").arg(name).arg(mimetype)); + } + + part->execute(item); +} + +//#include "executeaction.moc" diff --git a/kexi/plugins/macros/kexiactions/executeaction.h b/kexi/plugins/macros/kexiactions/executeaction.h new file mode 100644 index 00000000..17a8ca88 --- /dev/null +++ b/kexi/plugins/macros/kexiactions/executeaction.h @@ -0,0 +1,78 @@ +/*************************************************************************** + * This file is part of the KDE project + * copyright (C) 2006 by Sebastian Sauer (mail@dipe.org) + * copyright (C) 2006 by Bernd Steindorff (bernd@itii.de) + * copyright (C) 2006 by Sascha Kupper (kusato@kfnv.de) + * + * 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 KEXIMACRO_EXECUTEACTION_H +#define KEXIMACRO_EXECUTEACTION_H + +#include "kexiaction.h" + +class KexiMainWindow; + +namespace KoMacro { + class Context; +} + +namespace KexiMacro { + + /** + * The ExecuteAction class implements a @a KoMacro::Action + * to provide functionality to execute an object like + * e.g. a script or a macro. + */ + class ExecuteAction : public KexiAction + { + Q_OBJECT + public: + + /** + * Constructor. + */ + ExecuteAction(); + + /** + * Destructor. + */ + virtual ~ExecuteAction(); + + /** + * This function is called, when the @a KoMacro::Variable + * with name @p name used within the @a KoMacro::MacroItem + * @p macroitem got changed. + * + * @param macroitem The @a KoMacro::MacroItem instance where + * the variable defined with @p name is located in. + * @param name The name the @a KoMacro::Variable has. + * @return true if the update was successfully else false + * is returned. + */ + virtual bool notifyUpdated(KSharedPtr<KoMacro::MacroItem> macroitem, const QString& name); + + public slots: + + /** + * Called if the @a Action should be executed within the + * defined @p context . + */ + virtual void activate(KSharedPtr<KoMacro::Context> context); + + }; +} + +#endif diff --git a/kexi/plugins/macros/kexiactions/kexiaction.cpp b/kexi/plugins/macros/kexiactions/kexiaction.cpp new file mode 100644 index 00000000..521f8cfc --- /dev/null +++ b/kexi/plugins/macros/kexiactions/kexiaction.cpp @@ -0,0 +1,48 @@ +/*************************************************************************** + * This file is part of the KDE project + * copyright (C) 2006 by Sebastian Sauer (mail@dipe.org) + * copyright (C) 2006 by Bernd Steindorff (bernd@itii.de) + * copyright (C) 2006 by Sascha Kupper (kusato@kfnv.de) + * + * 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 "kexiaction.h" +#include "../lib/exception.h" + +#include <ksharedptr.h> + +using namespace KexiMacro; + +KexiAction::KexiAction(const QString& name, const QString& text) + : KoMacro::Action(name) +{ + m_mainwin = dynamic_cast< KexiMainWindow* >( KoMacro::Manager::self()->guiClient() ); + + if(! m_mainwin) { + throw KoMacro::Exception("Invalid KexiMainWindow instance."); + } + + // Set the caption this action has. + setText(text); +} + +KexiAction::~KexiAction() +{ +} + +KexiMainWindow* KexiAction::mainWin() const +{ + return m_mainwin; +} diff --git a/kexi/plugins/macros/kexiactions/kexiaction.h b/kexi/plugins/macros/kexiactions/kexiaction.h new file mode 100644 index 00000000..a61e2bc1 --- /dev/null +++ b/kexi/plugins/macros/kexiactions/kexiaction.h @@ -0,0 +1,75 @@ +/*************************************************************************** + * This file is part of the KDE project + * copyright (C) 2006 by Sebastian Sauer (mail@dipe.org) + * copyright (C) 2006 by Bernd Steindorff (bernd@itii.de) + * copyright (C) 2006 by Sascha Kupper (kusato@kfnv.de) + * + * 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 KEXIMACRO_KEXIACTION_H +#define KEXIMACRO_KEXIACTION_H + +#include "../lib/action.h" +#include "../lib/variable.h" +#include "../lib/macroitem.h" +#include "../lib/context.h" + +#include "objectvariable.h" +#include "objectnamevariable.h" + +#include <core/keximainwindow.h> + +namespace KexiMacro { + + /** + * Template class to offer common functionality needed by all + * @a KoMacro::Action implementations Kexi provides. + * + * All the actions Kexi provides are inherited from this + * template class. + */ + class KexiAction : public KoMacro::Action + { + public: + + /** + * Constructor. + * + * @param name The unique name the @a KoMacro::Action has. This + * name will be used to identify the action. + * @param text The i18n-caption text used for display purposes. + */ + KexiAction(const QString& name, const QString& text); + + /** + * Destructor. + */ + virtual ~KexiAction(); + + /** + * @return the @a KexiMainWindow instance we are + * running in. + */ + KexiMainWindow* mainWin() const; + + private: + + /// The @a KexiMainWindow instance. + KexiMainWindow* m_mainwin; + + }; +} + +#endif diff --git a/kexi/plugins/macros/kexiactions/kexivariable.h b/kexi/plugins/macros/kexiactions/kexivariable.h new file mode 100644 index 00000000..27dcc0ef --- /dev/null +++ b/kexi/plugins/macros/kexiactions/kexivariable.h @@ -0,0 +1,76 @@ +/*************************************************************************** + * This file is part of the KDE project + * copyright (C) 2006 by Sebastian Sauer (mail@dipe.org) + * copyright (C) 2006 by Bernd Steindorff (bernd@itii.de) + * copyright (C) 2006 by Sascha Kupper (kusato@kfnv.de) + * + * 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 KEXIMACRO_KEXIVARIABLE_H +#define KEXIMACRO_KEXIVARIABLE_H + +#include "../lib/manager.h" +#include "../lib/exception.h" +#include "../lib/action.h" +#include "../lib/variable.h" + +#include <ksharedptr.h> + +class KexiMainWindow; + +namespace KoMacro { + class Context; +} + +namespace KexiMacro { + + /** + * Template class to offer common functionality needed by all + * @a KoMacro::Variable implementations Kexi provides. + */ + template<class ACTIONIMPL> + class KexiVariable : public KoMacro::Variable + { + public: + + /** + * Constructor. + */ + KexiVariable(ACTIONIMPL* actionimpl, const QString& name, const QString& caption) + : KoMacro::Variable() + , m_actionimpl(actionimpl) + { + setName(name); + setText(caption); + } + + protected: + + /** + * @return the @a KexiAction implementation this @a KexiVariable + * is a child of. + */ + ACTIONIMPL* actionImpl() const + { + return m_actionimpl; + } + + private: + /// The parent @a KexiAction implementation. + ACTIONIMPL* m_actionimpl; + }; +} + +#endif diff --git a/kexi/plugins/macros/kexiactions/messageaction.cpp b/kexi/plugins/macros/kexiactions/messageaction.cpp new file mode 100644 index 00000000..1a4605cb --- /dev/null +++ b/kexi/plugins/macros/kexiactions/messageaction.cpp @@ -0,0 +1,50 @@ +/*************************************************************************** + * This file is part of the KDE project + * copyright (C) 2006 by Sebastian Sauer (mail@dipe.org) + * copyright (C) 2006 by Bernd Steindorff (bernd@itii.de) + * copyright (C) 2006 by Sascha Kupper (kusato@kfnv.de) + * + * 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 "messageaction.h" + +#include <core/keximainwindow.h> + +#include <klocale.h> +#include <kmessagebox.h> +#include <kdebug.h> + +using namespace KexiMacro; + +MessageAction::MessageAction() + : KexiAction("message", i18n("Message")) +{ + setVariable("caption", i18n("Caption"), QString("")); + setVariable("message", i18n("Message"), QString("")); +} + +MessageAction::~MessageAction() +{ +} + +void MessageAction::activate(KSharedPtr<KoMacro::Context> context) +{ + kdDebug() << "MessageAction::activate(KSharedPtr<Context>)" << endl; + const QString caption = context->variable("caption")->variant().toString(); + const QString message = context->variable("message")->variant().toString(); + KMessageBox::information(mainWin(), message, caption); +} + +//#include "messageaction.moc" diff --git a/kexi/plugins/macros/kexiactions/messageaction.h b/kexi/plugins/macros/kexiactions/messageaction.h new file mode 100644 index 00000000..543674bd --- /dev/null +++ b/kexi/plugins/macros/kexiactions/messageaction.h @@ -0,0 +1,66 @@ +/*************************************************************************** + * This file is part of the KDE project + * copyright (C) 2006 by Sebastian Sauer (mail@dipe.org) + * copyright (C) 2006 by Bernd Steindorff (bernd@itii.de) + * copyright (C) 2006 by Sascha Kupper (kusato@kfnv.de) + * + * 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 KEXIMACRO_MESSAGEACTION_H +#define KEXIMACRO_MESSAGEACTION_H + + +#include "kexiaction.h" + +class KexiMainWindow; + +namespace KoMacro { + class Context; +} + +namespace KexiMacro { + + /** + * The ExecuteObject class implements a @a KoMacro::Action + * to provide functionality to execute an object like + * e.g. a script or a macro. + */ + class MessageAction : public KexiAction + { + Q_OBJECT + public: + + /** + * Constructor. + */ + MessageAction(); + + /** + * Destructor. + */ + virtual ~MessageAction(); + + public slots: + + /** + * Called if the @a Action should be executed within the + * defined @param context . + */ + virtual void activate(KSharedPtr<KoMacro::Context> context); + + }; +} + +#endif diff --git a/kexi/plugins/macros/kexiactions/navigateaction.cpp b/kexi/plugins/macros/kexiactions/navigateaction.cpp new file mode 100644 index 00000000..d3fe551c --- /dev/null +++ b/kexi/plugins/macros/kexiactions/navigateaction.cpp @@ -0,0 +1,158 @@ +/*************************************************************************** + * This file is part of the KDE project + * copyright (C) 2006 by Sebastian Sauer (mail@dipe.org) + * copyright (C) 2006 by Bernd Steindorff (bernd@itii.de) + * copyright (C) 2006 by Sascha Kupper (kusato@kfnv.de) + * + * 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 "navigateaction.h" + +#include <core/kexi.h> +#include <core/kexiproject.h> +#include <core/kexipartmanager.h> +#include <core/kexipartinfo.h> +#include <core/kexipart.h> +#include <core/keximainwindow.h> +#include <core/kexidialogbase.h> + +#include <widget/kexidataawareview.h> +#include <widget/tableview/kexidataawareobjectiface.h> + +#include <klocale.h> +#include <kdebug.h> + +using namespace KexiMacro; + +namespace KexiMacro { + + template<class ACTIONIMPL> + class NavigateVariable : public KexiVariable<ACTIONIMPL> + { + public: + NavigateVariable(ACTIONIMPL* actionimpl) + : KexiVariable<ACTIONIMPL>(actionimpl, "record", i18n("Record")) + { + QStringList list; + list << "first" << "previous" << "next" << "last" << "goto"; + this->appendChild( KSharedPtr<KoMacro::Variable>( new KoMacro::Variable(list, "@list") ) ); + + /*TODO should this actions belong to navigate? maybe it would be more wise to have + such kind of functionality in an own e.g. "Modify" action to outline, that + we are manipulating the database that way... */ + //"add" << "save" << "delete" << "query" << "execute" << "cancel" << "reload" + + this->setVariant( list[0] ); + } + }; + +} + +NavigateAction::NavigateAction() + : KexiAction("navigate", i18n("Navigate")) +{ + KoMacro::Variable* navvar = new NavigateVariable<NavigateAction>(this); + setVariable(KSharedPtr<KoMacro::Variable>( navvar )); + + KoMacro::Variable* rowvar = new KexiVariable<NavigateAction>(this, "rownr", i18n("Row")); + rowvar->setVariant(0); + setVariable(KSharedPtr<KoMacro::Variable>(rowvar)); + + KoMacro::Variable* colvar = new KexiVariable<NavigateAction>(this, "colnr", i18n("Column")); + colvar->setVariant(0); + setVariable(KSharedPtr<KoMacro::Variable>(colvar)); +} + +NavigateAction::~NavigateAction() +{ +} + +bool NavigateAction::notifyUpdated(KSharedPtr<KoMacro::MacroItem> macroitem, const QString& name) +{ + kdDebug()<<"NavigateAction::notifyUpdated() name="<<name<<" macroitem.action="<<(macroitem->action() ? macroitem->action()->name() : "NOACTION")<<endl; + KSharedPtr<KoMacro::Variable> variable = macroitem->variable(name, false); + if(! variable) { + kdWarning()<<"NavigateAction::notifyUpdated() No such variable="<<name<<" in macroitem."<<endl; + return false; + } + + variable->clearChildren(); + if(name == "goto") { + const int rownr = macroitem->variant("rownr", true).toInt(); // e.g. "macro" or "script" + const int colnr = macroitem->variant("colnr", true).toInt(); // e.g. "macro1" or "macro2" if objectvalue above is "macro" + + macroitem->variable("rownr", true)->setChildren( + KoMacro::Variable::List() << KSharedPtr<KoMacro::Variable>(new KoMacro::Variable(rownr)) ); + macroitem->variable("colnr", true)->setChildren( + KoMacro::Variable::List() << KSharedPtr<KoMacro::Variable>(new KoMacro::Variable(colnr)) ); + } + + return true; +} + +void NavigateAction::activate(KSharedPtr<KoMacro::Context> context) +{ + KexiDialogBase* dialog = dynamic_cast<KexiDialogBase*>( mainWin()->activeWindow() ); + if(! dialog) { + throw KoMacro::Exception(i18n("No window active.")); + } + + KexiViewBase* view = dialog->selectedView(); + if(! view) { + throw KoMacro::Exception(i18n("No view selected for \"%1\".").arg(dialog->caption())); + } + + KexiDataAwareView* dbview = dynamic_cast<KexiDataAwareView*>( view ); + KexiDataAwareObjectInterface* dbobj = dbview ? dbview->dataAwareObject() : 0; + if(! dbview) { + throw KoMacro::Exception(i18n("The view for \"%1\" could not handle data.").arg(dialog->caption())); + } + + const QString record = context->variable("record")->variant().toString(); + if(record == "previous") { + dbobj->selectPrevRow(); + } + else if(record == "next") { + dbobj->selectNextRow(); + } + else if(record == "first") { + dbobj->selectFirstRow(); + } + else if(record == "last") { + dbobj->selectLastRow(); + } + else if(record == "goto") { + int rownr = context->variable("rownr")->variant().toInt() - 1; + int colnr = context->variable("colnr")->variant().toInt() - 1; + dbobj->setCursorPosition(rownr >= 0 ? rownr : dbobj->currentRow(), colnr >= 0 ? colnr : dbobj->currentColumn()); + } + else { + /* + virtual void selectNextPage(); //!< page down action + virtual void selectPrevPage(); //!< page up action + void deleteAllRows(); + void deleteCurrentRow(); + void deleteAndStartEditCurrentCell(); + void startEditOrToggleValue(); + bool acceptRowEdit(); + void cancelRowEdit(); + void sortAscending(); + void sortDescending(); + */ + throw KoMacro::Exception(i18n("Unknown record \"%1\" in view for \"%2\".").arg(record).arg(dialog->caption())); + } +} + +//#include "navigateaction.moc" diff --git a/kexi/plugins/macros/kexiactions/navigateaction.h b/kexi/plugins/macros/kexiactions/navigateaction.h new file mode 100644 index 00000000..f7f74f86 --- /dev/null +++ b/kexi/plugins/macros/kexiactions/navigateaction.h @@ -0,0 +1,78 @@ +/*************************************************************************** + * This file is part of the KDE project + * copyright (C) 2006 by Sebastian Sauer (mail@dipe.org) + * copyright (C) 2006 by Bernd Steindorff (bernd@itii.de) + * copyright (C) 2006 by Sascha Kupper (kusato@kfnv.de) + * + * 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 KEXIMACRO_NAVIGATEACTION_H +#define KEXIMACRO_NAVIGATEACTION_H + +#include "kexiaction.h" + +class KexiMainWindow; + +namespace KoMacro { + class Context; +} + +namespace KexiMacro { + + /** + * The NavigateAction class implements a @a KoMacro::Action + * to provide functionality to execute an object like + * e.g. a script or a macro. + */ + class NavigateAction : public KexiAction + { + Q_OBJECT + public: + + /** + * Constructor. + */ + NavigateAction(); + + /** + * Destructor. + */ + virtual ~NavigateAction(); + + /** + * This function is called, when the @a KoMacro::Variable + * with name @p name used within the @a KoMacro::MacroItem + * @p macroitem got changed. + * + * @param macroitem The @a KoMacro::MacroItem instance where + * the variable defined with @p name is located in. + * @param name The name the @a KoMacro::Variable has. + * @return true if the update was successfully else false + * is returned. + */ + virtual bool notifyUpdated(KSharedPtr<KoMacro::MacroItem> macroitem, const QString& name); + + public slots: + + /** + * Called if the @a Action should be executed within the + * defined @p context . + */ + virtual void activate(KSharedPtr<KoMacro::Context> context); + + }; +} + +#endif diff --git a/kexi/plugins/macros/kexiactions/objectnamevariable.h b/kexi/plugins/macros/kexiactions/objectnamevariable.h new file mode 100644 index 00000000..eeaabe04 --- /dev/null +++ b/kexi/plugins/macros/kexiactions/objectnamevariable.h @@ -0,0 +1,76 @@ +/*************************************************************************** + * This file is part of the KDE project + * copyright (C) 2006 by Sebastian Sauer (mail@dipe.org) + * copyright (C) 2006 by Bernd Steindorff (bernd@itii.de) + * copyright (C) 2006 by Sascha Kupper (kusato@kfnv.de) + * + * 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 KEXIMACRO_OBJECTNAMEVARIABLE_H +#define KEXIMACRO_OBJECTNAMEVARIABLE_H + +#include "../lib/variable.h" + +#include "kexivariable.h" + +#include <core/kexi.h> +#include <core/kexiproject.h> +#include <core/kexipartmanager.h> +#include <core/kexipartinfo.h> + +#include <klocale.h> + +namespace KexiMacro { + + /** + * The ViewVariable class provide a list of KexiPart::PartItem's + * supported by a KexiPart::Part as @a KoMacro::Variable . + */ + template<class ACTIONIMPL> + class ObjectNameVariable : public KexiVariable<ACTIONIMPL> + { + public: + ObjectNameVariable(ACTIONIMPL* actionimpl, const QString& objectname = QString::null, const QString& name = QString::null) + : KexiVariable<ACTIONIMPL>(actionimpl, "name", i18n("Name")) + { + if(! actionimpl->mainWin()->project()) + return; + + QStringList namelist; + KexiPart::Info* info = Kexi::partManager().infoForMimeType( QString("kexi/%1").arg(objectname) ); + if(info) { + KexiPart::ItemDict* items = actionimpl->mainWin()->project()->items(info); + if(items) + for(KexiPart::ItemDictIterator item_it = *items; item_it.current(); ++item_it) + namelist << item_it.current()->name(); + } + + if(namelist.count() <= 0) + namelist << ""; + + for(QStringList::Iterator it = namelist.begin(); it != namelist.end(); ++it) + this->appendChild( KSharedPtr<KoMacro::Variable>(new KoMacro::Variable(*it)) ); + + this->setVariant( (name.isNull() || ! namelist.contains(name)) ? namelist[0] : name ); + + kdDebug()<<"##################### KexiActions::ObjectNameVariable() objectname="<<objectname<<" name="<<name<<" value="<<this->variant()<<" children="<<namelist<<endl; + } + + virtual ~ObjectNameVariable() {} + }; + +} + +#endif diff --git a/kexi/plugins/macros/kexiactions/objectvariable.h b/kexi/plugins/macros/kexiactions/objectvariable.h new file mode 100644 index 00000000..b61f24e3 --- /dev/null +++ b/kexi/plugins/macros/kexiactions/objectvariable.h @@ -0,0 +1,87 @@ +/*************************************************************************** + * This file is part of the KDE project + * copyright (C) 2006 by Sebastian Sauer (mail@dipe.org) + * copyright (C) 2006 by Bernd Steindorff (bernd@itii.de) + * copyright (C) 2006 by Sascha Kupper (kusato@kfnv.de) + * + * 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 KEXIMACRO_OBJECTVARIABLE_H +#define KEXIMACRO_OBJECTVARIABLE_H + +#include "../lib/action.h" +#include "../lib/variable.h" + +#include "kexivariable.h" + +#include <core/kexi.h> +#include <core/kexiproject.h> +#include <core/kexipartmanager.h> +#include <core/kexipartinfo.h> + +#include <klocale.h> +#include <kdebug.h> + +namespace KexiMacro { + + /** + * The ObjectVariable class implements @a KoMacro::Variable to + * provide a variable list of Kexi-objects. Those Kexi-objects + * are KexiPart's like e.g. table, query, form or script. + */ + template<class ACTIONIMPL> + class ObjectVariable : public KexiVariable<ACTIONIMPL> + { + public: + + enum Conditions { + VisibleInNav = 1, + Executable = 2, + DataExport = 4 + }; + + ObjectVariable(ACTIONIMPL* actionimpl, int conditions = VisibleInNav, const QString& objectname = QString::null) + : KexiVariable<ACTIONIMPL>(actionimpl, "object", i18n("Object")) + { + KexiPart::PartInfoList* parts = Kexi::partManager().partInfoList(); + for(KexiPart::PartInfoListIterator it(*parts); it.current(); ++it) { + KexiPart::Info* info = it.current(); + + if(conditions & VisibleInNav && ! info->isVisibleInNavigator()) + continue; + if(conditions & Executable && ! info->isExecuteSupported()) + continue; + if(conditions & DataExport && ! info->isDataExportSupported()) + continue; + + const QString name = info->objectName(); //info->groupName(); + this->appendChild( KSharedPtr<KoMacro::Variable>(new KoMacro::Variable(name)) ); + } + + if(! objectname.isNull()) + this->setVariant( objectname ); + else if(this->children().count() > 0) + this->setVariant( this->children()[0]->variant() ); + else + this->setVariant( QString::null ); + + kdDebug()<<"##################### KexiActions::ObjectVariable() variant="<<this->variant()<<endl; + } + + }; + +} + +#endif diff --git a/kexi/plugins/macros/kexiactions/openaction.cpp b/kexi/plugins/macros/kexiactions/openaction.cpp new file mode 100644 index 00000000..b67041bb --- /dev/null +++ b/kexi/plugins/macros/kexiactions/openaction.cpp @@ -0,0 +1,154 @@ +/*************************************************************************** + * This file is part of the KDE project + * copyright (C) 2006 by Sebastian Sauer (mail@dipe.org) + * copyright (C) 2006 by Bernd Steindorff (bernd@itii.de) + * copyright (C) 2006 by Sascha Kupper (kusato@kfnv.de) + * + * 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 "openaction.h" + +#include <core/kexi.h> +#include <core/kexiproject.h> +#include <core/kexipartmanager.h> +#include <core/kexipartinfo.h> +#include <core/kexipart.h> +#include <core/keximainwindow.h> + +#include <klocale.h> + +using namespace KexiMacro; + +namespace KexiMacro { + + static const QString DATAVIEW = "data"; + static const QString DESIGNVIEW = "design"; + static const QString TEXTVIEW = "text"; + + static const QString OBJECT = "object"; + static const QString NAME = "name"; + static const QString VIEW = "view"; + + /** + * The ViewVariable class provide a list of viewmodes supported + * by a KexiPart::Part as @a KoMacro::Variable . + */ + template<class ACTIONIMPL> + class ViewVariable : public KexiVariable<ACTIONIMPL> + { + public: + ViewVariable(ACTIONIMPL* actionimpl, const QString& objectname = QString::null, const QString& viewname = QString::null) + : KexiVariable<ACTIONIMPL>(actionimpl, VIEW, i18n("View")) + { + QStringList namelist; + KexiPart::Part* part = Kexi::partManager().partForMimeType( QString("kexi/%1").arg(objectname) ); + if(part) { + int viewmodes = part->supportedViewModes(); + if(viewmodes & Kexi::DataViewMode) + namelist << DATAVIEW; + if(viewmodes & Kexi::DesignViewMode) + namelist << DESIGNVIEW; + if(viewmodes & Kexi::TextViewMode) + namelist << TEXTVIEW; + for(QStringList::Iterator it = namelist.begin(); it != namelist.end(); ++it) + this->children().append( KSharedPtr<KoMacro::Variable>(new KoMacro::Variable(*it)) ); + } + const QString n = + namelist.contains(viewname) + ? viewname + : namelist.count() > 0 ? namelist[0] : ""; + + this->setVariant(n); + } + }; + +} + +OpenAction::OpenAction() + : KexiAction("open", i18n("Open")) +{ + const int conditions = ObjectVariable<OpenAction>::VisibleInNav; + + KSharedPtr<KoMacro::Variable> objvar = new ObjectVariable<OpenAction>(this, conditions); + setVariable(objvar); + + setVariable(KSharedPtr<KoMacro::Variable>( new ObjectNameVariable<OpenAction>(this, objvar->variant().toString()) )); + setVariable(KSharedPtr<KoMacro::Variable>( new ViewVariable<OpenAction>(this, objvar->variant().toString()) )); +} + +OpenAction::~OpenAction() +{ +} + +bool OpenAction::notifyUpdated(KSharedPtr<KoMacro::MacroItem> macroitem, const QString& name) +{ + kdDebug()<<"OpenAction::notifyUpdated() name="<<name<<" macroitem.action="<<(macroitem->action() ? macroitem->action()->name() : "NOACTION")<<endl; + KSharedPtr<KoMacro::Variable> variable = macroitem->variable(name, false); + if(! variable) { + kdWarning()<<"OpenAction::notifyUpdated() No such variable="<<name<<" in macroitem."<<endl; + return false; + } + + variable->clearChildren(); + if(name == OBJECT) { + const QString objectvalue = macroitem->variant(OBJECT, true).toString(); // e.g. "table" or "query" + const QString objectname = macroitem->variant(NAME, true).toString(); // e.g. "table1" or "table2" if objectvalue above is "table" + const QString viewname = macroitem->variant(VIEW, true).toString(); // "data", "design" or "text" + + macroitem->variable(NAME, true)->setChildren( + KoMacro::Variable::List() << KSharedPtr<KoMacro::Variable>(new ObjectNameVariable<OpenAction>(this, objectvalue, objectname)) ); + macroitem->variable(VIEW, true)->setChildren( + KoMacro::Variable::List() << KSharedPtr<KoMacro::Variable>(new ViewVariable<OpenAction>(this, objectvalue, viewname)) ); + } + + return true; +} + +void OpenAction::activate(KSharedPtr<KoMacro::Context> context) +{ + if(! mainWin()->project()) { + throw KoMacro::Exception(i18n("No project loaded.")); + } + + const QString objectname = context->variable(OBJECT)->variant().toString(); + const QString name = context->variable(NAME)->variant().toString(); + KexiPart::Item* item = mainWin()->project()->itemForMimeType( QString("kexi/%1").arg(objectname).latin1(), name ); + if(! item) { + throw KoMacro::Exception(i18n("No such object \"%1.%2\".").arg(objectname).arg(name)); + } + + // Determinate the viewmode. + const QString view = context->variable(VIEW)->variant().toString(); + int viewmode; + if(view == DATAVIEW) + viewmode = Kexi::DataViewMode; + else if(view == DESIGNVIEW) + viewmode = Kexi::DesignViewMode; + else if(view == TEXTVIEW) + viewmode = Kexi::TextViewMode; + else { + throw KoMacro::Exception(i18n("No such viewmode \"%1\" in object \"%2.%3\".").arg(view).arg(objectname).arg(name)); + } + + // Try to open the object now. + bool openingCancelled; + if(! mainWin()->openObject(item, viewmode, openingCancelled)) { + if(! openingCancelled) { + throw KoMacro::Exception(i18n("Failed to open object \"%1.%2\".").arg(objectname).arg(name)); + } + } +} + +//#include "openaction.moc" diff --git a/kexi/plugins/macros/kexiactions/openaction.h b/kexi/plugins/macros/kexiactions/openaction.h new file mode 100644 index 00000000..b49f1238 --- /dev/null +++ b/kexi/plugins/macros/kexiactions/openaction.h @@ -0,0 +1,79 @@ +/*************************************************************************** + * This file is part of the KDE project + * copyright (C) 2006 by Sebastian Sauer (mail@dipe.org) + * copyright (C) 2006 by Bernd Steindorff (bernd@itii.de) + * copyright (C) 2006 by Sascha Kupper (kusato@kfnv.de) + * + * 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 KEXIMACRO_OPENACTION_H +#define KEXIMACRO_OPENACTION_H + +#include "kexiaction.h" + +class KexiMainWindow; + +namespace KoMacro { + class Context; +} + +namespace KexiMacro { + + /** + * The OpenAction class implements a @a KoMacro::Action + * to provide functionality to open any kind of Kexi + * object (e.g. table, query, form, script, ...). + */ + class OpenAction : public KexiAction + { + Q_OBJECT + + public: + + /** + * Constructor. + */ + OpenAction(); + + /** + * Destructor. + */ + virtual ~OpenAction(); + + /** + * This function is called, when the @a KoMacro::Variable + * with name @p name used within the @a KoMacro::MacroItem + * @p macroitem got changed. + * + * @param macroitem The @a KoMacro::MacroItem instance where + * the variable defined with @p name is located in. + * @param name The name the @a KoMacro::Variable has. + * @return true if the update was successfully else false + * is returned. + */ + virtual bool notifyUpdated(KSharedPtr<KoMacro::MacroItem> macroitem, const QString& name); + + public slots: + + /** + * Called if the @a Action should be executed within the + * defined @p context . + */ + virtual void activate(KSharedPtr<KoMacro::Context> context); + + }; +} + +#endif diff --git a/kexi/plugins/macros/kexipart/Makefile.am b/kexi/plugins/macros/kexipart/Makefile.am new file mode 100644 index 00000000..51cff0ea --- /dev/null +++ b/kexi/plugins/macros/kexipart/Makefile.am @@ -0,0 +1,32 @@ +include $(top_srcdir)/kexi/Makefile.global + +kde_module_LTLIBRARIES = kexihandler_macro.la + +kexihandler_macro_la_SOURCES = \ + keximacropart.cpp keximacroview.cpp keximacroproperty.cpp keximacrodesignview.cpp keximacrotextview.cpp keximacroerrorbase.ui keximacroerror.cpp + +kexihandler_macro_la_LDFLAGS = \ + $(KDE_PLUGIN) -module -no-undefined -Wnounresolved $(all_libraries) $(VER_INFO) + +kexihandler_macro_la_LIBADD = \ + ../kexiactions/libkeximacroactions.la \ + $(top_builddir)/kexi/core/libkexicore.la \ + $(top_builddir)/kexi/widget/libkexiextendedwidgets.la \ + $(top_builddir)/lib/koproperty/libkoproperty.la + +INCLUDES = \ + -I$(top_srcdir)/lib \ + -I$(top_srcdir)/lib/kofficecore/ \ + -I$(top_srcdir)/kexi/core \ + -I$(top_srcdir)/kexi \ + -I$(top_srcdir)/kexi/widget \ + $(all_includes) + +servicesdir=$(kde_servicesdir)/kexi +services_DATA=keximacrohandler.desktop + +SUBDIRS = . +METASOURCES = AUTO + +noinst_HEADERS = \ + keximacropart.h keximacroview.h keximacrodesignview.h keximacrotextview.h keximacroerror.h diff --git a/kexi/plugins/macros/kexipart/keximacrodesignview.cpp b/kexi/plugins/macros/kexipart/keximacrodesignview.cpp new file mode 100644 index 00000000..030be0cb --- /dev/null +++ b/kexi/plugins/macros/kexipart/keximacrodesignview.cpp @@ -0,0 +1,497 @@ +/* This file is part of the KDE project + Copyright (C) 2006 Sebastian Sauer <mail@dipe.org> + + 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 "keximacrodesignview.h" +#include "keximacroproperty.h" + +#include <qtimer.h> +#include <qdom.h> +#include <kdebug.h> + +#include <kexidialogbase.h> +#include <kexidb/connection.h> +#include <kexidb/error.h> + +#include <core/kexi.h> +#include <core/kexiproject.h> +#include <core/kexipartmanager.h> +#include <core/kexipartinfo.h> + +#include <widget/kexidatatable.h> +#include <widget/tableview/kexitableview.h> +#include <widget/tableview/kexitableviewdata.h> +#include <widget/tableview/kexitableitem.h> +#include <widget/tableview/kexidataawarepropertyset.h> + +#include <koproperty/set.h> +#include <koproperty/property.h> + +#include "../lib/macro.h" +#include "../lib/macroitem.h" +#include "../lib/xmlhandler.h" + +/// constants used to name columns instead of hardcoding indices +#define COLUMN_ID_ACTION 0 +#define COLUMN_ID_COMMENT 1 + +/** +* \internal d-pointer class to be more flexible on future extension of the +* functionality without to much risk to break the binary compatibility. +*/ +class KexiMacroDesignView::Private +{ + public: + + /** + * The view used to display the actions + * a \a Macro has. + */ + KexiDataTable* datatable; + + /** + * For convenience. The table view ( datatable->tableView() ). + */ + KexiTableView* tableview; + + /** + * The \a KexiTableViewData data-model for the + * \a KexiTableView above. + */ + KexiTableViewData* tabledata; + + /** + * The \a KexiDataAwarePropertySet is used to display + * properties an action provides in the propertyview. + */ + KexiDataAwarePropertySet* propertyset; + + /// Boolean flag to avoid infinite recursion. + bool reloadsProperties; + /// Boolean flag to avoid infinite recursion. + bool updatesProperties; + + /** + * Constructor. + * + * \param m The passed \a KoMacro::Manager instance our + * \a manager points to. + */ + Private() + : propertyset(0) + , reloadsProperties(false) + , updatesProperties(false) + { + } + + /** + * Destructor. + */ + ~Private() + { + delete propertyset; + } + +}; + +KexiMacroDesignView::KexiMacroDesignView(KexiMainWindow *mainwin, QWidget *parent, ::KoMacro::Macro* const macro) + : KexiMacroView(mainwin, parent, macro, "KexiMacroDesignView") + , d( new Private() ) +{ + // The table's data-model. + d->tabledata = new KexiTableViewData(); + d->tabledata->setSorting(-1); // disable sorting + + // Add the "Action" column. + KexiTableViewColumn* actioncol = new KexiTableViewColumn( + "action", // name/identifier + KexiDB::Field::Enum, // fieldtype + KexiDB::Field::NoConstraints, // constraints + KexiDB::Field::NoOptions, // options + 0, // length + 0, // precision + QVariant(), // default value + i18n("Action"), // caption + QString::null, // description + 0 // width + ); + d->tabledata->addColumn(actioncol); + + QValueVector<QString> items; + items.append(""); // empty means no action + + // Append the list of actions provided by Kexi. + QStringList actionnames = KoMacro::Manager::self()->actionNames(); + QStringList::ConstIterator it, end( actionnames.constEnd() ); + for( it = actionnames.constBegin(); it != end; ++it) { + KSharedPtr<KoMacro::Action> action = KoMacro::Manager::self()->action(*it); + items.append( action->text() ); + } + + actioncol->field()->setEnumHints(items); + + // Add the "Comment" column. + d->tabledata->addColumn( new KexiTableViewColumn( + "comment", // name/identifier + KexiDB::Field::Text, // fieldtype + KexiDB::Field::NoConstraints, // constraints + KexiDB::Field::NoOptions, // options + 0, // length + 0, // precision + QVariant(), // default value + i18n("Comment"), // caption + QString::null, // description + 0 // width + ) ); + + // Create the tableview. + QHBoxLayout* layout = new QHBoxLayout(this); + d->datatable = new KexiDataTable(mainWin(), this, "Macro KexiDataTable", false /*not db aware*/); + layout->addWidget(d->datatable); + d->tableview = d->datatable->tableView(); + d->tableview->setSpreadSheetMode(); + d->tableview->setColumnStretchEnabled( true, COLUMN_ID_COMMENT ); //last column occupies the rest of the area + + // We need to register our KexiMacroPropertyFactory to use our own + // KoProperty::Property implementation. + KexiMacroPropertyFactory::initFactory(); + + // Create the propertyset. + d->propertyset = new KexiDataAwarePropertySet(this, d->tableview); + + // Connect signals the KexiDataTable provides to local slots. + connect(d->tabledata, SIGNAL(aboutToChangeCell(KexiTableItem*,int,QVariant&,KexiDB::ResultInfo*)), + this, SLOT(beforeCellChanged(KexiTableItem*,int,QVariant&,KexiDB::ResultInfo*))); + connect(d->tabledata, SIGNAL(rowUpdated(KexiTableItem*)), + this, SLOT(rowUpdated(KexiTableItem*))); + connect(d->tabledata, SIGNAL(rowInserted(KexiTableItem*,uint,bool)), + this, SLOT(rowInserted(KexiTableItem*,uint,bool))); + connect(d->tabledata, SIGNAL(rowDeleted()), + this, SLOT(rowDeleted())); + + // Everything is ready. So, update the data now. + updateData(); + setDirty(false); +} + +KexiMacroDesignView::~KexiMacroDesignView() +{ + delete d; +} + +void KexiMacroDesignView::updateData() +{ + kdDebug() << "KexiMacroDesignView::updateData()" << endl; + + // Remove previous content of tabledata. + d->tabledata->deleteAllRows(); + // Remove old property sets. + d->propertyset->clear(); + + // Add some empty rows + for (int i=0; i<50; i++) { + d->tabledata->append( d->tabledata->createItem() ); + } + + // Set the MacroItem's + QStringList actionnames = KoMacro::Manager::self()->actionNames(); + KoMacro::MacroItem::List macroitems = macro()->items(); + KoMacro::MacroItem::List::ConstIterator it(macroitems.constBegin()), end(macroitems.constEnd()); + for(uint idx = 0; it != end; ++it, idx++) { + KexiTableItem* tableitem = d->tabledata->at(idx); + if(! tableitem) { + // If there exists no such item, add it. + tableitem = d->tabledata->createItem(); + d->tabledata->append(tableitem); + } + // Set the action-column. + KSharedPtr<KoMacro::Action> action = (*it)->action(); + if(action.data()) { + int i = actionnames.findIndex( action->name() ); + if(i >= 0) { + tableitem->at(COLUMN_ID_ACTION) = i + 1; + //setAction(tableitem, action->name()); + } + } + // Set the comment-column. + tableitem->at(COLUMN_ID_COMMENT) = (*it)->comment(); + } + + // set data for our spreadsheet: this will clear our sets + d->tableview->setData(d->tabledata); + + // Add the property sets. + it = macroitems.constBegin(); + for(uint idx = 0; it != end; ++it, idx++) { + updateProperties(idx, 0, *it); + } + + // work around a bug in the KexiTableView where we lose the stretch-setting... + d->tableview->setColumnStretchEnabled( true, COLUMN_ID_COMMENT ); //last column occupies the rest of the area + + propertySetReloaded(true); +} + +bool KexiMacroDesignView::loadData() +{ + if(! KexiMacroView::loadData()) { + return false; + } + updateData(); // update the tableview's data. + return true; +} + +KoProperty::Set* KexiMacroDesignView::propertySet() +{ + return d->propertyset->currentPropertySet(); +} + +void KexiMacroDesignView::beforeCellChanged(KexiTableItem* item, int colnum, QVariant& newvalue, KexiDB::ResultInfo* result) +{ + Q_UNUSED(result); + kdDebug() << "KexiMacroDesignView::beforeCellChanged() colnum=" << colnum << " newvalue=" << newvalue.toString() << endl; + + int rowindex = d->tabledata->findRef(item); + if(rowindex < 0) { + kdWarning() << "KexiMacroDesignView::beforeCellChanged() No such item" << endl; + return; + } + + // If the rowindex doesn't exists yet, we need to append new + // items till we are able to access the item we like to use. + for(int i = macro()->items().count(); i <= rowindex; i++) { + macro()->addItem( KSharedPtr<KoMacro::MacroItem>( new KoMacro::MacroItem() ) ); + } + + // Get the matching MacroItem. + KSharedPtr<KoMacro::MacroItem> macroitem = macro()->items()[rowindex]; + if(! macroitem.data()) { + kdWarning() << "KexiMacroDesignView::beforeCellChanged() Invalid item for rowindex=" << rowindex << endl; + return; + } + + // Handle the column that should be changed + switch(colnum) { + case COLUMN_ID_ACTION: { // The "Action" column + QString actionname; + bool ok; + int selectedindex = newvalue.toInt(&ok); + if(ok && selectedindex > 0) { + QStringList actionnames = KoMacro::Manager::self()->actionNames(); + actionname = actionnames[ selectedindex - 1 ]; // first item is empty + } + KSharedPtr<KoMacro::Action> action = KoMacro::Manager::self()->action(actionname); + macroitem->setAction(action); + updateProperties(d->propertyset->currentRow(), d->propertyset->currentPropertySet(), macroitem); + propertySetReloaded(true); + } break; + case COLUMN_ID_COMMENT: { // The "Comment" column + macroitem->setComment( newvalue.toString() ); + } break; + default: + kdWarning() << "KexiMacroDesignView::beforeCellChanged() No such column number " << colnum << endl; + return; + } + + setDirty(); +} + +void KexiMacroDesignView::rowUpdated(KexiTableItem* item) +{ + int rowindex = d->tabledata->findRef(item); + kdDebug() << "KexiMacroDesignView::rowUpdated() rowindex=" << rowindex << endl; + //propertySetSwitched(); + //propertySetReloaded(true); + //setDirty(); +} + +void KexiMacroDesignView::rowInserted(KexiTableItem*, uint row, bool) +{ + kdDebug() << "KexiMacroDesignView::rowInserted() rowindex=" << row << endl; + KoMacro::MacroItem::List& macroitems = macro()->items(); + + if(row < macroitems.count()) { + // If a new item was inserted, we need to insert a new item to our + // list of MacroItems too. If the new item was appended, we don't + // need to do anything yet cause the new item will be handled on + // beforeCellChanged() anyway. + kdDebug() << "KexiMacroDesignView::rowInserted() Inserting new MacroItem" << endl; + KSharedPtr<KoMacro::MacroItem> macroitem = KSharedPtr<KoMacro::MacroItem>( new KoMacro::MacroItem() ); + KoMacro::MacroItem::List::Iterator it = macroitems.at(row); + macroitems.insert(it, macroitem); + } +} + +void KexiMacroDesignView::rowDeleted() +{ + int rowindex = d->propertyset->currentRow(); + if(rowindex < 0) { + kdWarning() << "KexiMacroDesignView::rowDeleted() No such item" << endl; + return; + } + kdDebug() << "KexiMacroDesignView::rowDeleted() rowindex=" << rowindex << endl; + KoMacro::MacroItem::List& macroitems = macro()->items(); + macroitems.remove( macroitems.at(rowindex) ); +} + +bool KexiMacroDesignView::updateSet(KoProperty::Set* set, KSharedPtr<KoMacro::MacroItem> macroitem, const QString& variablename) +{ + kdDebug() << "KexiMacroDesignView::updateSet() variablename=" << variablename << endl; + KoProperty::Property* property = KexiMacroProperty::createProperty(macroitem, variablename); + if(! property) + return false; + set->addProperty(property); + return true; +} + +void KexiMacroDesignView::updateProperties(int row, KoProperty::Set* set, KSharedPtr<KoMacro::MacroItem> macroitem) +{ + kdDebug() << "KexiMacroDesignView::updateProperties() row=" << row << endl; + + if(row < 0 || d->updatesProperties) { + return; // ignore invalid rows and avoid infinite recursion. + } + + KSharedPtr<KoMacro::Action> action = macroitem->action(); + if(! action.data()) { + // don't display a propertyset if there is no action defined. + d->propertyset->remove(row); + return; // job done. + } + + d->updatesProperties = true; + + if(set) { + // we need to clear old data before adding the new content. + set->clear(); + } + else { + // if there exists no such propertyset yet, create one. + set = new KoProperty::Set(d->propertyset, action->name()); + d->propertyset->insert(row, set, true); + connect(set, SIGNAL(propertyChanged(KoProperty::Set&, KoProperty::Property&)), + this, SLOT(propertyChanged(KoProperty::Set&, KoProperty::Property&))); + } + + // The caption. + KoProperty::Property* prop = new KoProperty::Property("this:classString", action->text()); + prop->setVisible(false); + set->addProperty(prop); + + // Display the list of variables. + QStringList varnames = action->variableNames(); + for(QStringList::Iterator it = varnames.begin(); it != varnames.end(); ++it) { + if(updateSet(set, macroitem, *it)) { + KSharedPtr<KoMacro::Variable> variable = macroitem->variable(*it, true); + kdDebug()<<"KexiMacroDesignView::updateProperties() name=" << *it << " variable=" << variable->variant().toString() << endl; +#if 0 + macroitem->setVariable(*it, variable); +#endif + } + } + + d->updatesProperties = false; +} + +void KexiMacroDesignView::propertyChanged(KoProperty::Set& set, KoProperty::Property& property) +{ + Q_UNUSED(set); + kdDebug() << "!!!!! KexiMacroDesignView::propertyChanged() propertyname=" << property.name() << endl; + setDirty(); + + /* + if(d->reloadsProperties) // be sure to don't update properties if we are still on reloading. + return; + d->reloadsProperties = true; + + const int row = d->propertyset->currentRow(); + const QCString name = property.name(); + kdDebug() << "KexiMacroDesignView::propertyChanged() name=" << name << " row=" << row << endl; + + //TODO reload is only needed if something changed! + bool dirty = true; bool reload = true;//dirtyvarnames.count()>0; + + if(dirty || reload) { // Only reload properties if it's really needed. + setDirty(); + if(reload) { + // The MacroItem which should be changed. + KSharedPtr<KoMacro::MacroItem> macroitem = macro()->items()[row]; + // Update the properties. + updateProperties(row, &set, macroitem); + } + // It's needed to call the reload delayed cause in KoProperty::Editor + // QTimer::singleShot(10, this, SLOT(selectItemLater())); may lead + // to crashes if we are to fast. + QTimer::singleShot(50, this, SLOT(reloadPropertyLater())); + } + + d->reloadsProperties = false; + */ + + /* + QStringList dirtyvarnames = macroitem->setVariable(name, KSharedPtr<KoMacro::Variable>(pv)); + bool dirty = false; + bool reload = false; + for(QStringList::Iterator it = dirtyvarnames.begin(); it != dirtyvarnames.end(); ++it) { + KSharedPtr<KoMacro::Variable> variable = macroitem->variable(*it); + if(! variable.data()) { + kdDebug() << "KexiMacroDesignView::propertyChanged() name=" << name << " it=" << *it << " skipped cause such a variable is not known." << endl; + continue; + } + + if(! set.contains( (*it).latin1() )) { + // If there exist no such property yet, we need to add it. + if(updateSet(&set, macroitem, *it)) + reload = true; // we like to reload the whole set + continue; + } + + kdDebug() << "KexiMacroDesignView::propertyChanged() set existing property=" << *it << endl; + KoProperty::Property& p = set.property((*it).latin1()); + KoMacro::Variable::List children = variable->children(); + if(children.count() > 0) { + QStringList keys, names; + KoMacro::Variable::List::Iterator childit(children.begin()), childend(children.end()); + for(; childit != childend; ++childit) { + const QString s = (*childit)->variant().toString(); + keys << s; + names << s; + } + p.setListData( new KoProperty::Property::ListData(keys, names) ); + } + p.setValue(variable->variant()); + dirty = true; + } + + // If there are expired aka not any longer needed properties around, we + // need to reload the whole set. + for(KoProperty::Set::Iterator setit = set; setit.current(); ++setit) { + if(setit.currentKey() == name) continue; // don't remove ourself + if(setit.currentKey().left(5) == QCString("this:")) continue; // don't remove internal properties + if(setit.currentKey() == QCString("newrow")) continue; // also an internal used property + if(action.data() && action->hasVariable(setit.currentKey())) continue; // the property is still valid + reload = true; // we like to reload the whole set + } + */ +} + +void KexiMacroDesignView::reloadPropertyLater() +{ + propertySetReloaded(true); +} + +#include "keximacrodesignview.moc" + diff --git a/kexi/plugins/macros/kexipart/keximacrodesignview.h b/kexi/plugins/macros/kexipart/keximacrodesignview.h new file mode 100644 index 00000000..c3eca2d2 --- /dev/null +++ b/kexi/plugins/macros/kexipart/keximacrodesignview.h @@ -0,0 +1,129 @@ +/* This file is part of the KDE project + Copyright (C) 2006 Sebastian Sauer <mail@dipe.org> + + 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 KEXIMACRODESIGNVIEW_H +#define KEXIMACRODESIGNVIEW_H + +#include "keximacroview.h" + +// Forward declarations. +namespace KoMacro { + class Action; + class Macro; + class MacroItem; +} +namespace KoProperty { + class Property; +} +namespace KexiDB { + class ResultInfo; +} +class KexiTableItem; + +/** + * The KexiScriptDesignView implements \a KexiMacroView to provide + * a GUI-Editor to edit a Macro. + */ +class KexiMacroDesignView : public KexiMacroView +{ + Q_OBJECT + public: + + /** + * Constructor. + * + * \param mainwin The \a KexiMainWindow instance this \a KexiViewBase + * belongs to. + * \param parent The parent widget this widget should be displayed in. + * \param macro The \a KoMacro::Macro instance this view is for. + */ + KexiMacroDesignView(KexiMainWindow *mainwin, QWidget *parent, ::KoMacro::Macro* const macro); + + /** + * Destructor. + */ + virtual ~KexiMacroDesignView(); + + /** + * Load the data from XML source and fill the internally + * used \a KoMacro::Macro instance. + */ + virtual bool loadData(); + + /** + * \return the \a KoProperty::Set properties this view provides. + */ + virtual KoProperty::Set* propertySet(); + + private slots: + + /** + * Called before a cell changed in the internaly used + * \a KexiTableView . + */ + void beforeCellChanged(KexiTableItem*, int, QVariant&, KexiDB::ResultInfo*); + + /** + * Called if the passed \p item got updated. + */ + void rowUpdated(KexiTableItem* item); + + /** + * Called if a row got deleted. + */ + void rowDeleted(); + + /** + * Called if a row got inserted. + */ + void rowInserted(KexiTableItem* item, uint row, bool repaint); + + /** + * Called if a property in the \a KoProperty got changed. + */ + void propertyChanged(KoProperty::Set&, KoProperty::Property&); + + /** + * Reloads the propertyset delayed. + */ + void reloadPropertyLater(); + + private: + /// \internal d-pointer class. + class Private; + /// \internal d-pointer instance. + Private* const d; + + /** + * Update the table's data. + */ + void updateData(); + + /** + * Update the \a KoProperty::Set set with the passed \a KoMacro::MacroItem + * \p item and the variablename \p variablename . + */ + bool updateSet(KoProperty::Set* set, KSharedPtr<KoMacro::MacroItem> item, const QString& variablename); + + /** + * Update the properties of the \a KoProperty::Set \p set at + * row-number \p row with the \a KoMacro::MacroItem \p macroitem . + */ + void updateProperties(int row, KoProperty::Set* set, KSharedPtr<KoMacro::MacroItem> macroitem); +}; + +#endif diff --git a/kexi/plugins/macros/kexipart/keximacroerror.cpp b/kexi/plugins/macros/kexipart/keximacroerror.cpp new file mode 100644 index 00000000..15f4df3f --- /dev/null +++ b/kexi/plugins/macros/kexipart/keximacroerror.cpp @@ -0,0 +1,130 @@ +/*************************************************************************** + * This file is part of the KDE project + * copyright (C) 2006 by Tobi Krebs (tobi.krebs@gmail.com) + * copyright (C) 2006 by Bernd Steindorff (bernd@itii.de) + * copyright (C) 2006 by Sascha Kupper (kusato@kfnv.de) + * copyright (C) 2006 by Sebastian Sauer (mail@dipe.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 "keximacroerror.h" + +#include <core/kexiproject.h> +#include <core/keximainwindow.h> + +#include <qtimer.h> + +/** +* \internal d-pointer class to be more flexible on future extension of the +* functionality without to much risk to break the binary compatibility. +*/ +class KexiMacroError::Private +{ + public: + KexiMainWindow* const mainwin; + KSharedPtr<KoMacro::Context> context; + + Private(KexiMainWindow* const m, KoMacro::Context* const c) + : mainwin(m) + , context(c) + { + } +}; + +KexiMacroError::KexiMacroError(KexiMainWindow* mainwin, KSharedPtr<KoMacro::Context> context) + : KexiMacroErrorBase(mainwin, "KexiMacroError" , /*WFlags*/ Qt::WDestructiveClose) + , d(new Private(mainwin, context)) +{ + //setText(i18n("Execution failed")); //caption + //errortext, errorlist, continuebtn,cancelbtn, designerbtn + + KoMacro::Exception* exception = context->exception(); + + iconlbl->setPixmap(KGlobal::instance()->iconLoader()->loadIcon("messagebox_critical", KIcon::Small, 32)); + errorlbl->setText(i18n("<qt>Failed to execute the macro \"%1\".<br>%2</qt>").arg( context->macro()->name() ).arg( exception->errorMessage() )); + + int i = 1; + KoMacro::MacroItem::List items = context->macro()->items(); + for (KoMacro::MacroItem::List::ConstIterator mit = items.begin(); mit != items.end(); mit++) + { + KListViewItem* listviewitem = new KListViewItem(errorlist); + listviewitem->setText(0,QString("%1").arg(i++)); + listviewitem->setText(1,i18n("Action")); + KSharedPtr<KoMacro::MacroItem> macroitem = *mit; + + if (macroitem != 0 && macroitem->action() != 0) + { + listviewitem->setText(2,macroitem->action()->name()); + } + + if(macroitem == context->macroItem()) + { + listviewitem->setOpen(true); + listviewitem->setSelected(true); + errorlist->setSelected(listviewitem, true); + errorlist->ensureItemVisible(listviewitem); + } + + KoMacro::Variable::Map variables = macroitem->variables(); + KoMacro::Variable::Map::ConstIterator vit; + for ( vit = variables.begin(); vit != variables.end(); ++vit ) { + KListViewItem* child = new KListViewItem(listviewitem); + child->setText(1,vit.key()); + child->setText(2,vit.data()->toString()); + } + } + + connect(designerbtn, SIGNAL(clicked()), this, SLOT(designbtnClicked())); + connect(continuebtn, SIGNAL(clicked()), this, SLOT(continuebtnClicked())); +} + +KexiMacroError::~KexiMacroError() +{ + delete d; +} + +void KexiMacroError::designbtnClicked() +{ + if(! d->mainwin->project()) { + kdWarning() << QString("KexiMacroError::designbtnClicked(): No project open.") << endl; + return; + } + + // We need to determinate the KexiPart::Item which should be opened. + KSharedPtr<KoMacro::Macro> macro = d->context->macro(); + const QString name = macro->name(); + KexiPart::Item* item = d->mainwin->project()->itemForMimeType("kexi/macro", name); + if(! item) { + kdWarning() << QString("KexiMacroError::designbtnClicked(): No such macro \"%1\"").arg(name) << endl; + return; + } + + // Try to open the KexiPart::Item now. + bool openingCancelled; + if(! d->mainwin->openObject(item, Kexi::DesignViewMode, openingCancelled)) { + if(! openingCancelled) { + kdWarning() << QString("KexiMacroError::designbtnClicked(): Open macro \"%1\" in designview failed.").arg(name) << endl; + return; + } + } + + close(); +} + +void KexiMacroError::continuebtnClicked() +{ + QTimer::singleShot(200, d->context, SLOT(activateNext())); + close(); +} diff --git a/kexi/plugins/macros/kexipart/keximacroerror.h b/kexi/plugins/macros/kexipart/keximacroerror.h new file mode 100644 index 00000000..641859b7 --- /dev/null +++ b/kexi/plugins/macros/kexipart/keximacroerror.h @@ -0,0 +1,89 @@ +/*************************************************************************** + * This file is part of the KDE project + * copyright (C) 2006 by Tobi Krebs (tobi.krebs@gmail.com) + * copyright (C) 2006 by Bernd Steindorff (bernd@itii.de) + * copyright (C) 2006 by Sascha Kupper (kusato@kfnv.de) + * copyright (C) 2006 by Sebastian Sauer (mail@dipe.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 KEXIMACROERROR_H +#define KEXIMACROERROR_H + +#include <qwidget.h> +#include <qlabel.h> +#include <qpushbutton.h> + +#include <klistview.h> +#include <klocale.h> +#include <kiconloader.h> +#include <kglobal.h> +#include <kdebug.h> + +#include "../lib/context.h" +#include "../lib/exception.h" +#include "../lib/macro.h" +#include "../lib/macroitem.h" + +#include "keximacroerrorbase.h" + +// Forward-declarations. +class KexiMainWindow; + +/** +* An error dialog used to displayed more detailed informations about +* a @a KoMacro::Exception that got thrown within a @a KoMacro::Context +* during execution. +*/ +class KexiMacroError : public KexiMacroErrorBase +{ + Q_OBJECT + + public: + + /** + * Constructor. + * + * @param mainwin The parent @a KexiMainWindow instance. + * @param context The @a KoMacro::Context where the error happened. + */ + KexiMacroError(KexiMainWindow* mainwin, KSharedPtr<KoMacro::Context> context); + + /** + * Destructor. + */ + virtual ~KexiMacroError(); + + private slots: + + /** + * Called if the "Open Macrodesigner"-Button is clicked. + */ + void designbtnClicked(); + + /** + * Called if the "continue"-Button is clicked. + */ + void continuebtnClicked(); + + private: + /// \internal d-pointer class. + class Private; + /// \internal d-pointer instance. + Private* const d; + +}; + +#endif diff --git a/kexi/plugins/macros/kexipart/keximacroerrorbase.ui b/kexi/plugins/macros/kexipart/keximacroerrorbase.ui new file mode 100644 index 00000000..74e47cfc --- /dev/null +++ b/kexi/plugins/macros/kexipart/keximacroerrorbase.ui @@ -0,0 +1,213 @@ +<!DOCTYPE UI><UI version="3.3" stdsetdef="1"> +<class>KexiMacroErrorBase</class> +<widget class="QDialog"> + <property name="name"> + <cstring>KexiMacroErrorBase</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>492</width> + <height>424</height> + </rect> + </property> + <property name="caption"> + <string>Error</string> + </property> + <property name="sizeGripEnabled"> + <bool>true</bool> + </property> + <property name="modal"> + <bool>true</bool> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout4</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout8</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>iconlbl</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>48</width> + <height>48</height> + </size> + </property> + <property name="frameShape"> + <enum>NoFrame</enum> + </property> + <property name="text"> + <string></string> + </property> + <property name="alignment"> + <set>AlignVCenter|AlignLeft</set> + </property> + </widget> + <widget class="QLabel"> + <property name="name"> + <cstring>errorlbl</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>1</vsizetype> + <horstretch>1</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="frameShape"> + <enum>NoFrame</enum> + </property> + <property name="text"> + <string></string> + </property> + <property name="alignment"> + <set>WordBreak|AlignVCenter</set> + </property> + </widget> + </hbox> + </widget> + <widget class="KListView"> + <column> + <property name="text"> + <string>No</string> + </property> + <property name="clickable"> + <bool>false</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <column> + <property name="text"> + <string>Name</string> + </property> + <property name="clickable"> + <bool>false</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <column> + <property name="text"> + <string>Value</string> + </property> + <property name="clickable"> + <bool>false</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <property name="name"> + <cstring>errorlist</cstring> + </property> + <property name="allColumnsShowFocus"> + <bool>true</bool> + </property> + <property name="rootIsDecorated"> + <bool>true</bool> + </property> + <property name="resizeMode"> + <enum>LastColumn</enum> + </property> + <property name="fullWidth"> + <bool>true</bool> + </property> + <property name="itemsMovable"> + <bool>false</bool> + </property> + <property name="autoOpen"> + <bool>false</bool> + </property> + <property name="tooltipColumn"> + <number>1</number> + </property> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout1</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QPushButton"> + <property name="name"> + <cstring>designerbtn</cstring> + </property> + <property name="text"> + <string>Open in design view</string> + </property> + <property name="accel"> + <string></string> + </property> + <property name="autoDefault"> + <bool>true</bool> + </property> + </widget> + <widget class="QPushButton"> + <property name="name"> + <cstring>continuebtn</cstring> + </property> + <property name="text"> + <string>Continue</string> + </property> + </widget> + <widget class="QPushButton"> + <property name="name"> + <cstring>cancelbtn</cstring> + </property> + <property name="text"> + <string>Abort</string> + </property> + </widget> + </hbox> + </widget> + </vbox> + </widget> + </vbox> +</widget> +<customwidgets> +</customwidgets> +<connections> + <connection> + <sender>cancelbtn</sender> + <signal>clicked()</signal> + <receiver>KexiMacroErrorBase</receiver> + <slot>close()</slot> + </connection> +</connections> +<layoutdefaults spacing="6" margin="11"/> +<includehints> + <includehint>klistview.h</includehint> +</includehints> +</UI> diff --git a/kexi/plugins/macros/kexipart/keximacrohandler.desktop b/kexi/plugins/macros/kexipart/keximacrohandler.desktop new file mode 100644 index 00000000..c08a7b2a --- /dev/null +++ b/kexi/plugins/macros/kexipart/keximacrohandler.desktop @@ -0,0 +1,81 @@ +[Desktop Entry] +Type=Service +ServiceTypes=Kexi/Handler + +GenericName=Macros +GenericName[bg]=Макроси +GenericName[br]=Makroù +GenericName[da]=Makroer +GenericName[de]=Makros +GenericName[el]=Μακροεντολές +GenericName[eo]=Makrooj +GenericName[et]=Makrod +GenericName[fa]=کلاندستورها +GenericName[fy]=Macro's +GenericName[ga]=Macraí +GenericName[hr]=Makroi +GenericName[hu]=Makrók +GenericName[it]=Macro +GenericName[ja]=マクロ +GenericName[km]=ម៉ាក្រូ +GenericName[lv]=Makross +GenericName[nb]=Makroer +GenericName[nds]=Makros +GenericName[ne]=म्याक्रोस +GenericName[nl]=Macro's +GenericName[pl]=Makra +GenericName[ru]=Макросы +GenericName[se]=Makroat +GenericName[sk]=Makrá +GenericName[sl]=Makri +GenericName[sr]=Макрои +GenericName[sr@Latn]=Makroi +GenericName[sv]=Makron +GenericName[uk]=Макроси +GenericName[uz]=Makros +GenericName[uz@cyrillic]=Макрос +GenericName[zh_TW]=巨集 + +Name=Macros +Name[bg]=Макроси +Name[br]=Makroù +Name[da]=Makroer +Name[de]=Makros +Name[el]=Μακροεντολές +Name[eo]=Makrooj +Name[et]=Makrod +Name[fa]=کلاندستورها +Name[fy]=Macro's +Name[ga]=Macraí +Name[hr]=Makroi +Name[hu]=Makrók +Name[it]=Macro +Name[ja]=マクロ +Name[km]=ម៉ាក្រូ +Name[lv]=Makross +Name[nb]=Makroer +Name[nds]=Makros +Name[ne]=म्याक्रोस +Name[nl]=Macro's +Name[pl]=Makra +Name[ru]=Макросы +Name[se]=Makroat +Name[sk]=Makrá +Name[sl]=Makri +Name[sr]=Макрои +Name[sr@Latn]=Makroi +Name[sv]=Makron +Name[uk]=Макроси +Name[uz]=Makros +Name[uz@cyrillic]=Макрос +Name[zh_TW]=巨集 + +X-KDE-Library=kexihandler_macro +X-KDE-ParentApp=kexi +X-Kexi-PartVersion=2 +X-Kexi-TypeName=macro +X-Kexi-TypeMime=kexi/macro +X-Kexi-ItemIcon=macro +X-Kexi-SupportsDataExport=false +X-Kexi-SupportsPrinting=false +X-Kexi-SupportsExecution=true diff --git a/kexi/plugins/macros/kexipart/keximacropart.cpp b/kexi/plugins/macros/kexipart/keximacropart.cpp new file mode 100644 index 00000000..c4f020e4 --- /dev/null +++ b/kexi/plugins/macros/kexipart/keximacropart.cpp @@ -0,0 +1,172 @@ +/* This file is part of the KDE project + Copyright (C) 2006 Sebastian Sauer <mail@dipe.org> + + 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 "keximacropart.h" + +#include "keximacroview.h" +#include "keximacrodesignview.h" +#include "keximacrotextview.h" + +//#include "kexiviewbase.h" +//#include "keximainwindow.h" +//#include "kexiproject.h" + +#include <qdom.h> +#include <qstringlist.h> +#include <kgenericfactory.h> +#include <kexipartitem.h> +//#include <kxmlguiclient.h> +//#include <kexidialogbase.h> +//#include <kconfig.h> +//#include <kdebug.h> + +#include "../lib/manager.h" +#include "../lib/macro.h" +#include "../lib/macroitem.h" +#include "../lib/action.h" + +#include "../kexiactions/openaction.h" +#include "../kexiactions/executeaction.h" +#include "../kexiactions/navigateaction.h" +#include "../kexiactions/messageaction.h" +#include "../kexiactions/datatableaction.h" + +/** +* \internal d-pointer class to be more flexible on future extension of the +* functionality without to much risk to break the binary compatibility. +*/ +class KexiMacroPart::Private +{ + public: +}; + +KexiMacroPart::KexiMacroPart(QObject *parent, const char *name, const QStringList &l) + : KexiPart::Part(parent, name, l) + , d( new Private() ) +{ + //kdDebug() << "KexiMacroPart::KexiMacroPart() Ctor" << endl; + + //registered ID + m_registeredPartID = (int)KexiPart::MacroObjectType; + + //name of the instance. + 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.", + "macro"); + + //describing caption + m_names["instanceCaption"] = i18n("Macro"); + + //supported viewmodes + m_supportedViewModes = Kexi::DesignViewMode | Kexi::TextViewMode; +} + +KexiMacroPart::~KexiMacroPart() +{ + //kdDebug() << "KexiMacroPart::~KexiMacroPart() Dtor" << endl; + delete d; +} + +bool KexiMacroPart::execute(KexiPart::Item* item, QObject* sender) +{ + KexiDialogBase* dialog = new KexiDialogBase(m_mainWin); + dialog->setId( item->identifier() ); + KexiMacroView* view = dynamic_cast<KexiMacroView*>( createView(dialog, dialog, *item, Kexi::DataViewMode) ); + if(! view) { + kdWarning() << "KexiMacroPart::execute() Failed to create a view." << endl; + return false; + } + + if(! view->macro().data()) { + kdWarning() << "KexiMacroPart::execute() No such item " << item->name() << endl; + return false; + } + + kdDebug() << "KexiMacroPart::execute() itemname=" << item->name() << endl; + view->loadData(); + view->execute(sender); + view->deleteLater(); + return true; +} + +void KexiMacroPart::initPartActions() +{ + //kdDebug() << "KexiMacroPart::initPartActions()" << endl; + + KoMacro::Manager::init(m_mainWin); + new KexiMacro::OpenAction; + new KexiMacro::ExecuteAction; + new KexiMacro::DataTableAction; + new KexiMacro::NavigateAction; + new KexiMacro::MessageAction; +} + +void KexiMacroPart::initInstanceActions() +{ + //kdDebug() << "KexiMacroPart::initInstanceActions()" << endl; + //createSharedAction(Kexi::DesignViewMode, i18n("Execute Macro"), "exec", 0, "data_execute"); +} + +KexiViewBase* KexiMacroPart::createView(QWidget* parent, KexiDialogBase* dialog, KexiPart::Item& item, int viewMode, QMap<QString,QString>*) +{ + const QString itemname = item.name(); + //kdDebug() << "KexiMacroPart::createView() itemname=" << itemname << endl; + + if(! itemname.isNull()) { + KSharedPtr<KoMacro::Macro> macro = ::KoMacro::Manager::self()->getMacro(itemname); + if(! macro) { + // If we don't have a macro with that name yet, create one. + macro = ::KoMacro::Manager::self()->createMacro(itemname); + // and remember the new macro for later usage. + ::KoMacro::Manager::self()->addMacro(itemname, macro); + } + + KexiMainWindow *win = dialog->mainWin(); + if(win && win->project() && win->project()->dbConnection()) { + if(viewMode == Kexi::DesignViewMode) { + return new KexiMacroDesignView(win, parent, macro); + } + if(viewMode == Kexi::TextViewMode) { + return new KexiMacroTextView(win, parent, macro); + } + if(viewMode == Kexi::DataViewMode) { + // Called if the macro should be executed. + return new KexiMacroView(win, parent, macro); + } + } + } + + //kdDebug() << "KexiMacroPart::createView() No view available." << endl; + return 0; +} + +QString KexiMacroPart::i18nMessage(const QCString& englishMessage) const +{ + if(englishMessage=="Design of object \"%1\" has been modified.") { + return i18n("Design of macro \"%1\" has been modified."); + } + if(englishMessage=="Object \"%1\" already exists.") { + return i18n("Macro \"%1\" already exists."); + } + return englishMessage; +} + +K_EXPORT_COMPONENT_FACTORY( kexihandler_macro, KGenericFactory<KexiMacroPart>("kexihandler_macro") ) + +#include "keximacropart.moc" diff --git a/kexi/plugins/macros/kexipart/keximacropart.h b/kexi/plugins/macros/kexipart/keximacropart.h new file mode 100644 index 00000000..8d2d7af2 --- /dev/null +++ b/kexi/plugins/macros/kexipart/keximacropart.h @@ -0,0 +1,95 @@ +/* This file is part of the KDE project + Copyright (C) 2006 Sebastian Sauer <mail@dipe.org> + + 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 KEXIMACROPART_H +#define KEXIMACROPART_H + +//#include <qcstring.h> + +#include <kexi.h> +#include <kexipart.h> +#include <kexidialogbase.h> + +/** + * Kexi Macro Plugin. + */ +class KexiMacroPart : public KexiPart::Part +{ + Q_OBJECT + + public: + + /** + * Constructor. + * + * \param parent The parent QObject this part is child of. + * \param name The name this part has. + * \param args Optional list of arguments passed to this part. + */ + KexiMacroPart(QObject *parent, const char *name, const QStringList& args); + + /** + * Destructor. + */ + virtual ~KexiMacroPart(); + + /** + * Implementation of the KexiPart::Part::action method used to + * provide scripts as KAction's to the outside world. + */ + virtual bool execute(KexiPart::Item* item, QObject* sender = 0); + + /** + * \return the i18n message for the passed \p englishMessage string. + */ + virtual QString i18nMessage(const QCString& englishMessage) const; + + protected: + + /** + * Create a new view. + * + * \param parent The parent QWidget the new view is displayed in. + * \param dialog The \a KexiDialogBase the view is child of. + * \param item The \a KexiPart::Item this view is for. + * \param viewMode The viewmode we like to have a view for. + */ + virtual KexiViewBase* createView(QWidget *parent, + KexiDialogBase* dialog, + KexiPart::Item& item, + int viewMode = Kexi::DesignViewMode, + QMap<QString,QString>* staticObjectArgs = 0); + + /** + * Initialize the part's actions. + */ + virtual void initPartActions(); + + /** + * Initialize the instance actions. + */ + virtual void initInstanceActions(); + + private: + /// \internal d-pointer class. + class Private; + /// \internal d-pointer instance. + Private* const d; +}; + +#endif + diff --git a/kexi/plugins/macros/kexipart/keximacroproperty.cpp b/kexi/plugins/macros/kexipart/keximacroproperty.cpp new file mode 100644 index 00000000..2fdafd28 --- /dev/null +++ b/kexi/plugins/macros/kexipart/keximacroproperty.cpp @@ -0,0 +1,626 @@ +/* This file is part of the KDE project + Copyright (C) 2006 Sebastian Sauer <mail@dipe.org> + + 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 "keximacroproperty.h" + +#include <qlayout.h> +#include <qlineedit.h> +#include <qlistbox.h> +#include <qpainter.h> + +#include <kcombobox.h> +#include <kpushbutton.h> +#include <klocale.h> +#include <kiconloader.h> +#include <kdebug.h> + +#include "../lib/variable.h" +#include "../lib/macroitem.h" + +#define KEXIMACRO_PROPERTYEDITORTYPE 5682 + +/************************************************************* + * KexiMacroProperty + */ + +/** +* @internal d-pointer class to be more flexible on future extension of the +* functionality without to much risk to break the binary compatibility. +*/ +class KexiMacroProperty::Private +{ + public: + /** The @a KoMacro::MacroItem the custom property uses + internal. Together with the name we are able to identify + the used variable at runtime. */ + KSharedPtr<KoMacro::MacroItem> macroitem; + /** The name the variable @a KoMacro::Variable is known + as in the @a KoMacro::MacroItem defined above. */ + QString name; +}; + +KexiMacroProperty::KexiMacroProperty(KoProperty::Property* parent, KSharedPtr<KoMacro::MacroItem> macroitem, const QString& name) + : KoProperty::CustomProperty(parent) + , d( new Private() ) +{ + d->macroitem = macroitem; + d->name = name; + init(); +} + +KexiMacroProperty::~KexiMacroProperty() +{ + delete d; +} + +void KexiMacroProperty::init() +{ + Q_ASSERT( d->macroitem != 0 ); + //kdDebug() << "--------- KexiMacroProperty::set() macroitem=" << d->macroitem->name() << " name=" << d->name << endl; + + KSharedPtr<KoMacro::Action> action = d->macroitem->action(); + KSharedPtr<KoMacro::Variable> actionvariable = action->variable(d->name); + if(! actionvariable.data()) { + kdDebug() << "KexiMacroProperty::createProperty() Skipped cause there exists no such action=" << d->name << endl; + return; + } + + KSharedPtr<KoMacro::Variable> variable = d->macroitem->variable(d->name, true/*checkaction*/); + if(! variable.data()) { + kdDebug() << "KexiMacroProperty::createProperty() Skipped cause there exists no such variable=" << d->name << endl; + return; + } + + //TESTCASE!!!!!!!!!!!!!!!!!!!!!! + //if(! variable->isEnabled()) qFatal( QString("############## VARIABLE=%1").arg(variable->name()).latin1() ); + + Q_ASSERT(! d->name.isNull()); + m_property->setName( d->name.latin1() ); + m_property->setCaption( actionvariable->text() ); + m_property->setDescription( action->comment() ); + m_property->setValue( variable->variant(), true ); + m_property->setType( KEXIMACRO_PROPERTYEDITORTYPE ); // use our own propertytype +} + +KoProperty::Property* KexiMacroProperty::parentProperty() const +{ + return m_property; +} + +void KexiMacroProperty::setValue(const QVariant &value, bool rememberOldValue) +{ + Q_UNUSED(rememberOldValue); + kdDebug()<<"KexiMacroProperty::setValue name="<<d->name<<" value="<<value<<" rememberOldValue="<<rememberOldValue<<endl; + if(! d->macroitem->setVariant(d->name, value)) { // takes care of the type-conversation + kdDebug()<<"KexiMacroProperty::setValue Update failed !!!"<<endl; + return; + } + + // m_property->setValue() does check if the value changed by using + // this-value() and cause we already set it above, m_property->setValue() + // will be aborted. Well, we don't touch the properties value and handle + // it all via our CustomProperty class anyway. So, just ignore the property. + //m_property->setValue(this->value(), rememberOldValue, false/*useCustomProperty*/); + + emit valueChanged(); +} + +QVariant KexiMacroProperty::value() const +{ + KSharedPtr<KoMacro::Variable> variable = d->macroitem->variable(d->name, true); + Q_ASSERT( variable.data() != 0 ); + return variable.data() ? variable->variant() : QVariant(); +} + +bool KexiMacroProperty::handleValue() const +{ + return true; // we handle getting and setting of values and don't need KoProperty::Property for it. +} + +KSharedPtr<KoMacro::MacroItem> KexiMacroProperty::macroItem() const +{ + return d->macroitem; +} + +QString KexiMacroProperty::name() const +{ + return d->name; +} + +KSharedPtr<KoMacro::Variable> KexiMacroProperty::variable() const +{ + return d->macroitem->variable(d->name, true/*checkaction*/); +} + +KoProperty::Property* KexiMacroProperty::createProperty(KSharedPtr<KoMacro::MacroItem> macroitem, const QString& name) +{ + KoProperty::Property* property = new KoProperty::Property(); + KexiMacroProperty* customproperty = new KexiMacroProperty(property, macroitem, name); + if(! customproperty->variable().data()) { + kdWarning() << "KexiMacroProperty::createProperty() No such variable" << endl; + delete customproperty; customproperty = 0; + delete property; property = 0; + return 0; + } + property->setCustomProperty(customproperty); + return property; +} + +/************************************************************* + * KexiMacroPropertyFactory + */ + +KexiMacroPropertyFactory::KexiMacroPropertyFactory(QObject* parent) + : KoProperty::CustomPropertyFactory(parent) +{ +} + +KexiMacroPropertyFactory::~KexiMacroPropertyFactory() +{ +} + +KoProperty::CustomProperty* KexiMacroPropertyFactory::createCustomProperty(KoProperty::Property* parent) +{ + kdDebug()<<"KexiMacroPropertyFactory::createCustomProperty parent="<<parent->name()<<endl; + + KoProperty::CustomProperty* customproperty = parent->customProperty(); + KexiMacroProperty* parentcustomproperty = dynamic_cast<KexiMacroProperty*>(customproperty); + if(! parentcustomproperty) { + kdWarning() << "KexiMacroPropertyFactory::createCustomProperty() parent=" << parent->name() << " has an invalid customproperty." << endl; + return 0; + } + + KSharedPtr<KoMacro::MacroItem> macroitem = parentcustomproperty->macroItem(); + Q_ASSERT( macroitem.data() != 0 ); + const QString name = parentcustomproperty->name(); + Q_ASSERT(! name.isEmpty()); + + KexiMacroProperty* macroproperty = new KexiMacroProperty(parent, macroitem, name); + if(! macroproperty->variable().data()) { + delete macroproperty; macroproperty = 0; + return 0; + } + + return macroproperty; +} + +KoProperty::Widget* KexiMacroPropertyFactory::createCustomWidget(KoProperty::Property* property) +{ + kdDebug()<<"KexiMacroPropertyFactory::createCustomWidget property="<<property->name()<<endl; + return new KexiMacroPropertyWidget(property); +} + +void KexiMacroPropertyFactory::initFactory() +{ + CustomPropertyFactory* factory = KoProperty::FactoryManager::self()->factoryForEditorType(KEXIMACRO_PROPERTYEDITORTYPE); + if(! factory) { + factory = new KexiMacroPropertyFactory( KoProperty::FactoryManager::self() ); + KoProperty::FactoryManager::self()->registerFactoryForEditor(KEXIMACRO_PROPERTYEDITORTYPE, factory); + } +} + +/************************************************************* + * KexiMacroPropertyWidget + */ + +/** +* @internal implementation of a QListBoxItem to display the items of the +* combobox used within @a KexiMacroPropertyWidget to handle variables +* within a @a ListBox instance. +*/ +class ListBoxItem : public QListBoxText +{ + public: + ListBoxItem(QListBox* listbox) + : QListBoxText(listbox), m_enabled(true) {} + ListBoxItem(QListBox* listbox, const QString& text, QListBoxItem* after) + : QListBoxText(listbox, text, after), m_enabled(true) {} + virtual ~ListBoxItem() {} + void setEnabled(bool enabled) { m_enabled = enabled; } + virtual int width(const QListBox* lb) const { + Q_ASSERT( dynamic_cast<KComboBox*>( lb->parent() ) ); + return static_cast<KComboBox*>( lb->parent() )->lineEdit()->width() + 2; + } + virtual int height(const QListBox* lb) const { + Q_ASSERT( dynamic_cast<KComboBox*>( lb->parent() ) ); + return m_enabled ? static_cast<KComboBox*>( lb->parent() )->height() + 2 : 0; + } + private: + bool m_enabled; +}; + +/** +* @internal implementation of a @a ListBoxItem to provide an editable +* @a KoProperty::Widget as QListBoxItem in a @a ListBox instance. +*/ +class EditListBoxItem : public ListBoxItem +{ + public: + + EditListBoxItem(QListBox* listbox, KexiMacroProperty* macroproperty) + : ListBoxItem(listbox) + , m_macroproperty(macroproperty) + , m_prop(0) + , m_widget(0) + { + init(); + } + + virtual ~EditListBoxItem() { + delete m_widget; + delete m_prop; + } + + virtual QString text() const { + KSharedPtr<KoMacro::Variable> variable = m_macroproperty->variable(); + Q_ASSERT( variable.data() ); + //kdDebug()<<"EditListBoxItem::text() text="<<variable->toString()<<endl; + Q_ASSERT( variable->toString() != QString::null ); + return variable->toString(); + } + + KoProperty::Widget* widget() const { return m_widget; } + KSharedPtr<KoMacro::MacroItem> macroItem() const { return m_macroproperty->macroItem(); } + KSharedPtr<KoMacro::Variable> variable() const { return m_macroproperty->variable(); } + KSharedPtr<KoMacro::Action> action() const { return m_macroproperty->macroItem()->action(); } + + protected: + virtual void paint(QPainter* p) { + if(! m_widget) return; + Q_ASSERT( dynamic_cast<KComboBox*>( listBox()->parent() ) ); + const int w = width(listBox()); + const int h = height(listBox()); + m_widget->setFixedSize(w - 2, h - 2); + p->drawPixmap(0, 0, QPixmap::grabWidget(m_widget), 1, 1, w - 1, h - 1); + } + + private: + void init() { + KSharedPtr<KoMacro::MacroItem> macroitem = m_macroproperty->macroItem(); + Q_ASSERT( macroitem.data() ); + KSharedPtr<KoMacro::Action> action = m_macroproperty->macroItem()->action(); + if(! action.data()) { + kdWarning() << "EditListBoxItem::EditListBoxItem() Skipped cause there exists no action for macroproperty=" << m_macroproperty->name() << endl; + return; + } + KoProperty::Property* parentproperty = m_macroproperty->parentProperty(); + if(! parentproperty) { + kdWarning() << "EditListBoxItem::EditListBoxItem() No parentproperty defined" << endl; + return; + } + KSharedPtr<KoMacro::Variable> variable = m_macroproperty->variable(); + if(! variable.data()) { + kdWarning() << "EditListBoxItem::EditListBoxItem() No variable defined for property=" << parentproperty->name() << endl; + return; + } + + QVariant variant = variable->variant(); + + KSharedPtr<KoMacro::Variable> actionvariable = action->variable(m_macroproperty->name()); + if(actionvariable.data()) { + QVariant actionvariant = actionvariable->variant(); + Q_ASSERT( ! actionvariant.isNull() ); + Q_ASSERT( variant.canCast(actionvariant.type()) ); + variant.cast( actionvariant.type() ); //preserve type. + } + + int type = KoProperty::Auto; + switch(variant.type()) { + case QVariant::UInt: + case QVariant::Int: { + type = KoProperty::Integer; + } break; + case QVariant::CString: + case QVariant::String: { + type = KoProperty::String; + } break; + default: { + kdWarning() << "EditListBoxItem::EditListBoxItem() name=" << variable->name() << " type=" << QVariant::typeToName(variant.type()) << endl; + } break; + } + + QString name = variable->name(); + Q_ASSERT(! name.isNull()); + //if(name.isNull()) name = "aaaaaaaaaaaaaaaaa";//TESTCASE + m_prop = new KoProperty::Property( + name.latin1(), // name + variant, // value + variable->text(), // caption + QString::null, // description + type, // type + 0 //parentproperty // parent + ); + + m_widget = KoProperty::FactoryManager::self()->createWidgetForProperty(m_prop); + Q_ASSERT( m_widget != 0 ); + //m_widget->reparent(listBox()->viewport(), 0, QPoint(0,0)); + m_widget->reparent(listBox(), 0, QPoint(1,1)); + //layout->addWidget(m_widget, 1); + m_widget->setMinimumHeight(5); + m_widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + } + + private: + KexiMacroProperty* m_macroproperty; + KoProperty::Property* m_prop; + KoProperty::Widget* m_widget; +}; + +/** +* @internal implementation of a @a QListBox for the combobox used within +* @a KexiMacroPropertyWidget to handle different variable-states. +*/ +class ListBox : public QListBox +{ + public: + ListBox(KComboBox* parent, KexiMacroProperty* macroproperty) + : QListBox(parent) + , m_macroproperty(macroproperty) + , m_edititem(0) + { + viewport()->setBackgroundMode(PaletteBackground); + setVariableHeight(true); + update(); + } + + virtual ~ListBox() {} + + void update() { + m_items.clear(); + delete m_edititem; + m_edititem = 0; + clear(); + + m_edititem = new EditListBoxItem(this, m_macroproperty); + Q_ASSERT( m_edititem->widget() != 0 ); + + const QString name = m_macroproperty->name(); + KoMacro::Variable::List children; + { + KoMacro::Variable::List actionchildren; + + KSharedPtr<KoMacro::Variable> itemvar = m_macroproperty->macroItem()->variable(name,false); + //kdDebug() << "KexiMacroProperty::ListBox::update() itemvar="<<(itemvar.data() ? "name:"+itemvar->name()+" value:"+itemvar->toString() : "NULL")<<endl; + if(itemvar.data()) + actionchildren = itemvar->children(); + + KSharedPtr<KoMacro::Action> action = m_edititem->action(); + KSharedPtr<KoMacro::Variable> actionvar = action.data() ? action->variable(name) : KSharedPtr<KoMacro::Variable>(); + //kdDebug() << "KexiMacroProperty::ListBox::update() actionvar="<<(actionvar.data() ? "name:"+actionvar->name()+" value:"+actionvar->toString() : "NULL")<<endl; + if(actionvar.data()) + actionchildren += actionvar->children(); + + KoMacro::Variable::List::ConstIterator it(actionchildren.constBegin()), end(actionchildren.constEnd()); + for(; it != end; ++it) { + if(name == (*it)->name()) { + KoMacro::Variable::List list = (*it)->children(); + KoMacro::Variable::List::ConstIterator listit(list.constBegin()), listend(list.constEnd()); + for(; listit != listend; ++listit) + children.append( *listit ); + } + } + + if(children.count() <= 0) + children = actionchildren; + } + + /* + kdDebug() << "KexiMacroProperty::ListBox::update() name="<<name<<" childcount="<<children.count()<<endl; + KoMacro::Variable::List::ConstIterator listit(children.constBegin()), listend(children.constEnd()); + for(; listit != listend; ++listit) { + kdDebug()<<" child name="<<(*listit)->name()<<" value="<<(*listit)->toString()<<" childcount="<<(*listit)->children().count()<<endl; + } + */ + + if(children.count() > 0) { + KoMacro::Variable::List::Iterator childit(children.begin()), childend(children.end()); + for(; childit != childend; ++childit) { + const QString n = (*childit)->name(); + //if(! n.startsWith("@")) continue; + const QVariant v = (*childit)->variant(); + + //kdDebug() << " child name=" << n << " value=" << v << endl; + switch( v.type() ) { + /* case QVariant::Map: { + const QMap<QString,QVariant> map = v.toMap(); + for(QMap<QString,QVariant>::ConstIterator it = map.constBegin(); it != map.constEnd(); ++it) + m_items.append(it.key()); + } break; */ + case QVariant::List: { + const QValueList<QVariant> list = v.toList(); + for(QValueList<QVariant>::ConstIterator it = list.constBegin(); it != list.constEnd(); ++it) { + const QString s = (*it).toString().stripWhiteSpace(); + if(! s.isEmpty()) + m_items.append(s); + } + } break; + case QVariant::StringList: { + const QStringList list = v.toStringList(); + for(QStringList::ConstIterator it = list.constBegin(); it != list.constEnd(); ++it) + if(! (*it).isEmpty()) + m_items.append(*it); + } break; + default: { + const QString s = v.toString().stripWhiteSpace(); + if(! s.isEmpty()) + m_items.append(s); + } break; + } + } + } + + QListBoxItem* item = m_edititem; + const uint count = m_items.count(); + for(uint i = 0; i < count; i++) + item = new ListBoxItem(this, m_items[i], item); + } + + EditListBoxItem* editItem() const { return m_edititem; } + QStringList items() const { return m_items; } + + virtual void hide () { + QListBox::hide(); + for(uint i = 0; i < count(); i++) + static_cast<ListBoxItem*>( item(i) )->setEnabled(false); + } + virtual void show() { + update(); + adjustSize(); + QListBox::show(); + } + + private: + KexiMacroProperty* m_macroproperty; + EditListBoxItem* m_edititem; + QStringList m_items; +}; + +/** +* @internal d-pointer class to be more flexible on future extension of the +* functionality without to much risk to break the binary compatibility. +*/ +class KexiMacroPropertyWidget::Private +{ + public: + KexiMacroProperty* macroproperty; + KComboBox* combobox; + ListBox* listbox; +}; + +KexiMacroPropertyWidget::KexiMacroPropertyWidget(KoProperty::Property* property, QWidget* parent) + : KoProperty::Widget(property, parent) + , d( new Private() ) +{ + kdDebug() << "KexiMacroPropertyWidget::KexiMacroPropertyWidget() Ctor" << endl; + + QHBoxLayout* layout = new QHBoxLayout(this, 0, 0); + + d->macroproperty = dynamic_cast<KexiMacroProperty*>( property->customProperty() ); + if(! d->macroproperty) { + kdWarning() << "KexiMacroPropertyWidget::KexiMacroPropertyWidget() Missing macroproperty for property=" << property->name() << endl; + return; + } + + d->combobox = new KComboBox(this); + layout->addWidget(d->combobox); + d->listbox = new ListBox(d->combobox, d->macroproperty); + d->combobox->setEditable(true); + d->combobox->setListBox(d->listbox); + d->combobox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + d->combobox->setMinimumHeight(5); + d->combobox->setInsertionPolicy(QComboBox::NoInsertion); + d->combobox->setMinimumSize(10, 0); // to allow the combo to be resized to a small size + d->combobox->setAutoCompletion(false); + d->combobox->setContextMenuEnabled(false); + + QVariant value = d->macroproperty->value(); + int index = d->listbox->items().findIndex( value.toString() ); + if(index >= 0) { + d->combobox->setCurrentItem(index + 1); + d->listbox->setCurrentItem(index + 1); + } + else { + Q_ASSERT( d->listbox->editItem()->widget() != 0 ); + d->listbox->editItem()->widget()->setValue( d->macroproperty->value(), true ); + //d->combobox->setCurrentItem(0); + } + kdDebug() << ">>> KexiMacroPropertyWidget::KexiMacroPropertyWidget() CurrentItem=" << d->combobox->currentItem() << endl; + + d->combobox->setFocusProxy( d->listbox->editItem()->widget() ); + setFocusWidget( d->combobox->lineEdit() ); + + connect(d->combobox, SIGNAL(textChanged(const QString&)), + this, SLOT(slotComboBoxChanged())); + connect(d->combobox, SIGNAL(activated(int)), + this, SLOT(slotComboBoxActivated())); + connect(d->listbox->editItem()->widget(), SIGNAL(valueChanged(Widget*)), + this, SLOT(slotWidgetValueChanged())); + connect(d->macroproperty, SIGNAL(valueChanged()), + this, SLOT(slotPropertyValueChanged())); +} + +KexiMacroPropertyWidget::~KexiMacroPropertyWidget() +{ + kdDebug() << "KexiMacroPropertyWidget::~KexiMacroPropertyWidget() Dtor" << endl; + delete d; +} + +QVariant KexiMacroPropertyWidget::value() const +{ + kdDebug()<<"KexiMacroPropertyWidget::value() value="<<d->macroproperty->value()<<endl; + return d->macroproperty->value(); + /* QVariant value = d->combobox->currentText(); + value.cast( d->macroproperty->value().type() ); + return value; */ +} + +void KexiMacroPropertyWidget::setValue(const QVariant& value, bool emitChange) +{ + kdDebug()<<"KexiMacroPropertyWidget::setValue() value="<<value<<" emitChange="<<emitChange<<endl; + + if(! emitChange) + d->combobox->blockSignals(true); + + const QString s = value.toString(); + d->combobox->setCurrentText( s.isNull() ? "" : s ); + + if(emitChange) + emit valueChanged(this); + else + d->combobox->blockSignals(false); +} + +void KexiMacroPropertyWidget::setReadOnlyInternal(bool readOnly) +{ + Q_UNUSED(readOnly); + //kdDebug()<<"KexiMacroPropertyWidget::setReadOnlyInternal() readOnly="<<readOnly<<endl; +} + +void KexiMacroPropertyWidget::slotComboBoxChanged() +{ + kdDebug()<<"KexiMacroPropertyWidget::slotComboBoxChanged()"<<endl; + const QVariant v = d->combobox->currentText(); + d->macroproperty->setValue(v, true); + //emit valueChanged(this); +} + +void KexiMacroPropertyWidget::slotComboBoxActivated() +{ + Q_ASSERT( d->listbox->editItem()->widget() ); + const int index = d->combobox->currentItem(); + QString text = (index == 0) + ? d->listbox->editItem()->widget()->value().toString() + : d->combobox->text(index); + kdDebug()<<"KexiMacroPropertyWidget::slotComboBoxActivated() index="<<index<<" text="<<text<<endl; + d->combobox->setCurrentText(text); + slotWidgetValueChanged(); +} + +void KexiMacroPropertyWidget::slotWidgetValueChanged() +{ + d->macroproperty->emitPropertyChanged(); +} + +void KexiMacroPropertyWidget::slotPropertyValueChanged() +{ + Q_ASSERT( d->listbox->editItem()->widget() ); + const QVariant v = d->macroproperty->value(); + kdDebug()<<"KexiMacroPropertyWidget::slotPropertyValueChanged() value="<<v<<endl; + d->listbox->editItem()->widget()->setValue(v, true); +} + +#include "keximacroproperty.moc" diff --git a/kexi/plugins/macros/kexipart/keximacroproperty.h b/kexi/plugins/macros/kexipart/keximacroproperty.h new file mode 100644 index 00000000..19f7b7ac --- /dev/null +++ b/kexi/plugins/macros/kexipart/keximacroproperty.h @@ -0,0 +1,186 @@ +/* This file is part of the KDE project + Copyright (C) 2006 Sebastian Sauer <mail@dipe.org> + + 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 KEXIMACROPROPERTY_H +#define KEXIMACROPROPERTY_H + +#include <ksharedptr.h> +#include <koproperty/property.h> +#include <koproperty/factory.h> +#include <koproperty/customproperty.h> +#include <koproperty/widget.h> + +namespace KoMacro { + class Variable; + class MacroItem; +} + +class KexiMacroPropertyWidget; + +/** +* Implementation of a @a KoProperty::CustomProperty to have +* more control about the handling of our macro-properties. +*/ +class KexiMacroProperty + : public QObject + , public KoProperty::CustomProperty +{ + Q_OBJECT + + friend class KexiMacroPropertyWidget; + + public: + + /** Constructor. */ + explicit KexiMacroProperty(KoProperty::Property* parent, KSharedPtr<KoMacro::MacroItem> macroitem, const QString& name); + /** Destructor. */ + virtual ~KexiMacroProperty(); + + /** @return the parent @a KoProperty::Property instance. */ + KoProperty::Property* parentProperty() const; + + /** This function is called by @ref KoProperty::Property::setValue() + when a custom property is set. + You don't have to modify the property value, it is done by Property class. + You just have to update child or parent properties value (m_property->parent()->setValue()). + Note that, when calling Property::setValue, you <b>need</b> to set + useCustomProperty (3rd parameter) to false, or there will be infinite recursion. */ + virtual void setValue(const QVariant &value, bool rememberOldValue); + + /** This function is called by @ref KoProperty::Property::value() + when a custom property is set and @ref handleValue() is true. + You should return property's value, taken from parent's value.*/ + virtual QVariant value() const; + + /** Tells whether CustomProperty should be used to get the property's value. + You should return true for child properties, and false for others. */ + virtual bool handleValue() const; + + /** \return the \a KoMacro::MacroItem this custom property has or + NULL if there was no item provided. */ + KSharedPtr<KoMacro::MacroItem> macroItem() const; + + /** \return the name the property has in the \a KoMacro::MacroItem + above. Is QString::null if there was no item provided. */ + QString name() const; + + /** \return the \a KoMacro::Variable which has the name @a name() + in the item @a macroItem() . If such a variable doesn't exists NULL + is returned. */ + KSharedPtr<KoMacro::Variable> variable() const; + + /** Factory function to create a new @a KoProperty::Property instance + that will use a @a KexiMacroProperty as container. */ + static KoProperty::Property* createProperty(KSharedPtr<KoMacro::MacroItem> macroitem, const QString& name); + + signals: + + /** Emitted if @a setValue was called and the value changed. */ + void valueChanged(); + + private: + /** \internal d-pointer class. */ + class Private; + /** \internal d-pointer instance. */ + Private* const d; + /** \internal initializer method. */ + inline void init(); +}; + +/** +* Implementation of a @a KoProperty::CustomPropertyFactory to handle +* creation of @a KexiMacroProperty and @a KexiMacroPropertyWidget +* instances for our macro-properties. +*/ +class KexiMacroPropertyFactory : public KoProperty::CustomPropertyFactory +{ + public: + /** Constructor. */ + explicit KexiMacroPropertyFactory(QObject* parent); + /** Destructor. */ + virtual ~KexiMacroPropertyFactory(); + + /** @return a new instance of custom property for @p parent. + Implement this for property types you want to support. + Use parent->type() to get type of the property. */ + virtual KoProperty::CustomProperty* createCustomProperty(KoProperty::Property* parent); + + /** @return a new instance of custom property for @p property. + Implement this for property editor types you want to support. + Use parent->type() to get type of the property. */ + virtual KoProperty::Widget* createCustomWidget(KoProperty::Property* property); + + /** Initializes this factory. The factory may register itself at + the @a KoProperty::FactoryManager if not alreadydone before. This + function should be called from within the @a KexiMacroDesignView + before the functionality provided with @a KexiMacroProperty and + @a KexiMacroPropertyWidget got used. */ + static void initFactory(); +}; + +/** + * Implementation of a @a KoProperty::Widget used to display and + * edit a @a KexiMacroProperty . + */ +class KexiMacroPropertyWidget : public KoProperty::Widget +{ + Q_OBJECT + + public: + /** Constructor. */ + explicit KexiMacroPropertyWidget(KoProperty::Property* property, QWidget* parent = 0); + /** Destructor. */ + virtual ~KexiMacroPropertyWidget(); + + /** @return the value this widget has. */ + virtual QVariant value() const; + + /** Set the value @p value this widget has. If @p emitChange is true, + the @p KoProperty::Widget::valueChanged signal will be emitted. */ + virtual void setValue(const QVariant& value, bool emitChange=true); + + //virtual void drawViewer(QPainter *p, const QColorGroup &cg, const QRect &r, const QVariant &value); + + protected: + + /** Called if the value should be read only. */ + virtual void setReadOnlyInternal(bool readOnly); + + private slots: + + /** Called if the text in the KComboBox changed. */ + void slotComboBoxChanged(); + + /** Called if an item in the QListBox of the KComboBox got activated. */ + void slotComboBoxActivated(); + + /** Called if the @a KoProperty::Widget of the EditListBoxItem got changed. */ + void slotWidgetValueChanged(); + + /** Called if the value of a @a KexiMacroProperty changed to update + the widget and the displayed content. */ + void slotPropertyValueChanged(); + + private: + /** \internal d-pointer class. */ + class Private; + /** \internal d-pointer instance. */ + Private* const d; +}; + +#endif + diff --git a/kexi/plugins/macros/kexipart/keximacrotextview.cpp b/kexi/plugins/macros/kexipart/keximacrotextview.cpp new file mode 100644 index 00000000..95c94a47 --- /dev/null +++ b/kexi/plugins/macros/kexipart/keximacrotextview.cpp @@ -0,0 +1,90 @@ +/* This file is part of the KDE project + Copyright (C) 2006 Sebastian Sauer <mail@dipe.org> + + 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 "keximacrotextview.h" + +#include <ktextedit.h> +#include <kdebug.h> + +#include <kexidialogbase.h> +#include <kexidb/connection.h> + +#include "../lib/macro.h" +#include "../lib/xmlhandler.h" + +/** +* \internal d-pointer class to be more flexible on future extension of the +* functionality without to much risk to break the binary compatibility. +*/ +class KexiMacroTextView::Private +{ + public: + + /** + * The Editor used to display and edit the XML text. + */ + KTextEdit* editor; + +}; + +KexiMacroTextView::KexiMacroTextView(KexiMainWindow *mainwin, QWidget *parent, ::KoMacro::Macro* const macro) + : KexiMacroView(mainwin, parent, macro, "KexiMacroTextView") + , d( new Private() ) +{ + QHBoxLayout* layout = new QHBoxLayout(this); + d->editor = new KTextEdit(this); + d->editor->setTextFormat(Qt::PlainText); + d->editor->setWordWrap(QTextEdit::NoWrap); + layout->addWidget(d->editor); + + connect(d->editor, SIGNAL(textChanged()), this, SLOT(editorChanged())); +} + +KexiMacroTextView::~KexiMacroTextView() +{ + delete d; +} + +void KexiMacroTextView::editorChanged() +{ + setDirty(true); +} + +bool KexiMacroTextView::loadData() +{ + QString data; + if(! loadDataBlock(data)) { + kexipluginsdbg << "KexiMacroTextView::loadData(): no DataBlock" << endl; + return false; + } + + kdDebug() << QString("KexiMacroTextView::loadData()\n%1").arg(data) << endl; + //d->editor->blockSignals(true); + d->editor->setText(data); + //d->editor->blockSignals(false); + setDirty(false); + return true; +} + +tristate KexiMacroTextView::storeData(bool /*dontAsk*/) +{ + kexipluginsdbg << QString("KexiMacroTextView::storeData() %1 [%2]\n%3").arg(parentDialog()->partItem()->name()).arg(parentDialog()->id()).arg(d->editor->text()) << endl; + return storeDataBlock( d->editor->text() ); +} + +#include "keximacrotextview.moc" + diff --git a/kexi/plugins/macros/kexipart/keximacrotextview.h b/kexi/plugins/macros/kexipart/keximacrotextview.h new file mode 100644 index 00000000..66a2229c --- /dev/null +++ b/kexi/plugins/macros/kexipart/keximacrotextview.h @@ -0,0 +1,77 @@ +/* This file is part of the KDE project + Copyright (C) 2006 Sebastian Sauer <mail@dipe.org> + + 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 KEXIMACROTEXTVIEW_H +#define KEXIMACROTEXTVIEW_H + +#include "keximacroview.h" + +// Forward declaration. +namespace KoMacro { + class Macro; +} + +/** + * The KexiMacroTextView implements \a KexiMacroView to provide + * a simple texteditor to edit the XML document of a Macro. + */ +class KexiMacroTextView : public KexiMacroView +{ + Q_OBJECT + public: + + /** + * Constructor. + * + * \param mainwin The \a KexiMainWindow instance this \a KexiViewBase + * belongs to. + * \param parent The parent widget this widget should be displayed in. + * \param macro The \a KoMacro::Macro instance this view is for. + */ + KexiMacroTextView(KexiMainWindow *mainwin, QWidget *parent, ::KoMacro::Macro* const macro); + + /** + * Destructor. + */ + virtual ~KexiMacroTextView(); + + /** + * Load the data and display it in the editor. + */ + virtual bool loadData(); + + /** + * Try to store the modified data in the already opened and + * currently used \a KexiDB::SchemaData instance. + */ + virtual tristate storeData(bool dontAsk = false); + + private slots: + + /** + * This slot got called if the text of the editor changed. + */ + void editorChanged(); + + private: + /// \internal d-pointer class. + class Private; + /// \internal d-pointer instance. + Private* const d; +}; + +#endif diff --git a/kexi/plugins/macros/kexipart/keximacroview.cpp b/kexi/plugins/macros/kexipart/keximacroview.cpp new file mode 100644 index 00000000..35200829 --- /dev/null +++ b/kexi/plugins/macros/kexipart/keximacroview.cpp @@ -0,0 +1,175 @@ +/* This file is part of the KDE project + Copyright (C) 2006 Sebastian Sauer <mail@dipe.org> + + 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 "keximacroview.h" + +#include <qdom.h> +#include <kdebug.h> + +#include <kexidialogbase.h> +#include <kexidb/connection.h> +#include <kexidb/error.h> + +#include <core/kexi.h> +#include <core/kexiproject.h> +#include <core/kexipartmanager.h> +#include <core/kexipartinfo.h> + +#include "../lib/macro.h" +#include "../lib/xmlhandler.h" +#include "../lib/exception.h" + +#include "keximacroerror.h" + +/** +* \internal d-pointer class to be more flexible on future extension of the +* functionality without to much risk to break the binary compatibility. +*/ +class KexiMacroView::Private +{ + public: + + /** + * The \a KoMacro::Manager instance used to access the + * Macro Framework. + */ + KSharedPtr<KoMacro::Macro> macro; + + /** + * Constructor. + * + * \param m The passed \a KoMacro::Manager instance our + * \a manager points to. + */ + Private(KoMacro::Macro* const m) + : macro(m) + { + } + +}; + +KexiMacroView::KexiMacroView(KexiMainWindow *mainwin, QWidget *parent, KoMacro::Macro* const macro, const char* name) + : KexiViewBase(mainwin, parent, (name ? name : "KexiMacroView")) + , d( new Private(macro) ) +{ + //kdDebug() << "KexiMacroView::KexiMacroView() Ctor" << endl; + plugSharedAction( "data_execute", this, SLOT(execute()) ); +} + +KexiMacroView::~KexiMacroView() +{ + //kdDebug() << "KexiMacroView::~KexiMacroView() Dtor" << endl; + delete d; +} + +KSharedPtr<KoMacro::Macro> KexiMacroView::macro() const +{ + return d->macro; +} + +tristate KexiMacroView::beforeSwitchTo(int mode, bool& dontstore) +{ + kexipluginsdbg << "KexiMacroView::beforeSwitchTo mode=" << mode << " dontstore=" << dontstore << endl; + return true; +} + +tristate KexiMacroView::afterSwitchFrom(int mode) +{ + kexipluginsdbg << "KexiMacroView::afterSwitchFrom mode=" << mode << endl; + loadData(); // reload the data + return true; +} + +bool KexiMacroView::loadData() +{ + d->macro->clearItems(); + + QString data; + if(! loadDataBlock(data)) { + kexipluginsdbg << "KexiMacroView::loadData(): no DataBlock" << endl; + return false; + } + + QString errmsg; + int errline, errcol; + + QDomDocument domdoc; + bool parsed = domdoc.setContent(data, false, &errmsg, &errline, &errcol); + + if(! parsed) { + kexipluginsdbg << "KexiMacroView::loadData() XML parsing error line: " << errline << " col: " << errcol << " message: " << errmsg << endl; + return false; + } + + kexipluginsdbg << QString("KexiMacroView::loadData()\n%1").arg(domdoc.toString()) << endl; + QDomElement macroelem = domdoc.namedItem("macro").toElement(); + if(macroelem.isNull()) { + kexipluginsdbg << "KexiMacroView::loadData() Macro domelement is null" << endl; + return false; + } + + //kexipluginsdbg << "KexiMacroView::loadData()" << endl; + return d->macro->parseXML(macroelem); +} + +KexiDB::SchemaData* KexiMacroView::storeNewData(const KexiDB::SchemaData& sdata, bool &cancel) +{ + KexiDB::SchemaData *schema = KexiViewBase::storeNewData(sdata, cancel); + kexipluginsdbg << "KexiMacroView::storeNewData() new id:" << schema->id() << endl; + + if(!schema || cancel) { + delete schema; + return 0; + } + + if(! storeData()) { + kexipluginsdbg << "KexiMacroView::storeNewData() Failed to store the data." << endl; + //failure: remove object's schema data to avoid garbage + KexiDB::Connection *conn = parentDialog()->mainWin()->project()->dbConnection(); + conn->removeObject( schema->id() ); + delete schema; + return 0; + } + + return schema; +} + +tristate KexiMacroView::storeData(bool /*dontAsk*/) +{ + QDomDocument domdoc("macros"); + QDomElement macroelem = d->macro->toXML(); + domdoc.appendChild(macroelem); + const QString xml = domdoc.toString(2); + const QString name = QString("%1 [%2]").arg(parentDialog()->partItem()->name()).arg(parentDialog()->id()); + kexipluginsdbg << QString("KexiMacroView::storeData %1\n%2").arg(name).arg(xml) << endl; + return storeDataBlock(xml); +} + +void KexiMacroView::execute(QObject* sender) +{ + KSharedPtr<KoMacro::Context> context = d->macro->execute(sender); + if(context->hadException()) { + KexiMacroError* error = new KexiMacroError( + mainWin(), // The parent KexiMainWindow + context // The KoMacro::Context where the error occured. + ); + error->exec(); + } +} + +#include "keximacroview.moc" + diff --git a/kexi/plugins/macros/kexipart/keximacroview.h b/kexi/plugins/macros/kexipart/keximacroview.h new file mode 100644 index 00000000..beed842e --- /dev/null +++ b/kexi/plugins/macros/kexipart/keximacroview.h @@ -0,0 +1,140 @@ +/* This file is part of the KDE project + Copyright (C) 2006 Sebastian Sauer <mail@dipe.org> + + 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 KEXIMACROVIEW_H +#define KEXIMACROVIEW_H + +#include <kexiviewbase.h> + +// Forward declarations. +namespace KoMacro { + class Macro; +} +namespace KoProperty { + class Property; +} +namespace KexiDB { + class ResultInfo; +} +class KexiTableItem; + +/** + * The KexiMacroView implements \a KexiViewBase to provide + * a base KexiView instance for Macros. + * + * The \a KexiMacroDesignView and the \a KexiMacroTextView + * are inherited from this class. + */ +class KexiMacroView : public KexiViewBase +{ + Q_OBJECT + public: + + /** + * Constructor. + * + * \param mainwin The \a KexiMainWindow instance this \a KexiViewBase + * belongs to. + * \param parent The parent widget this widget should be displayed in. + * \param macro The \a KoMacro::Macro instance this view is for. + */ + KexiMacroView(KexiMainWindow *mainwin, QWidget *parent, ::KoMacro::Macro* const macro, const char* name = 0); + + /** + * Destructor. + */ + virtual ~KexiMacroView(); + + /** + * \return the Macro instance. + */ + KSharedPtr<KoMacro::Macro> macro() const; + + /** + * Load the data from XML source and fill the internally + * used \a KoMacro::Macro instance. + */ + virtual bool loadData(); + + /** + * Try to call \a storeData with new data we like to store. On + * success the matching \a KexiDB::SchemaData is returned. + * + * \param sdata The source \a KexiDB::SchemaData instance. + * \param cancel Cancel on failure and don't try to clean + * possible temporary created data up. + * \return The matching \a KexiDB::SchemaData instance or NULL + * if storing failed. + */ + virtual KexiDB::SchemaData* storeNewData(const KexiDB::SchemaData& sdata, bool &cancel); + + /** + * Try to store the modified data in the already opened and + * currently used \a KexiDB::SchemaData instance. + */ + virtual tristate storeData(bool dontAsk = false); + + public slots: + + /** + * This slot will be invoked if Kexi's menuitem Data=>Execute + * got activated and will execute the Macro. + */ + void execute(QObject* sender = 0); + + protected: + + /** + * Called by \a KexiDialogBase::switchToViewMode() right before dialog + * is switched to new mode. + * + * \param mode The viewmode to which should be switched. This + * could be either Kexi::DataViewMode, Kexi::DesignViewMode + * or Kexi::TextViewMode. + * \param donstore This call-by-reference boolean value defines + * if \a storeData should be called for the old but still + * selected viewmode. Set \a dontstore to true (it's false + * by default) if you want to avoid data storing. + * \return true if you accept or false if a error occupied and view + * shouldn't change. If there is no error but switching + * should be just cancelled (probably after showing some + * info messages), you need to return cancelled. + */ + virtual tristate beforeSwitchTo(int mode, bool& dontstore); + + /** + * Called by \a KexiDialogBase::switchToViewMode() right after dialog + * is switched to new mode. + * + * \param mode The viewmode to which we switched. This could + * be either Kexi::DataViewMode, Kexi::DesignViewMode + * or Kexi::TextViewMode. + * \return true if you accept or false if a error occupied and view + * shouldn't change. If there is no error but switching + * should be just cancelled (probably after showing + * some info messages), you need to return cancelled. + */ + virtual tristate afterSwitchFrom(int mode); + + private: + /// \internal d-pointer class. + class Private; + /// \internal d-pointer instance. + Private* const d; +}; + +#endif diff --git a/kexi/plugins/macros/lib/Makefile.am b/kexi/plugins/macros/lib/Makefile.am new file mode 100644 index 00000000..fc7867b8 --- /dev/null +++ b/kexi/plugins/macros/lib/Makefile.am @@ -0,0 +1,23 @@ +noinst_LTLIBRARIES = libkomacro.la + +libkomacro_la_SOURCES = \ + exception.cpp \ + variable.cpp \ + metaparameter.cpp \ + metamethod.cpp \ + metaobject.cpp \ + action.cpp \ + macroitem.cpp \ + macro.cpp \ + context.cpp \ + xmlhandler.cpp \ + manager.cpp + +KDE_CXXFLAGS = $(USE_EXCEPTIONS) + +libkomacro_la_LDFLAGS = $(all_libraries) -Wnounresolved +libkomacro_la_LIBADD = $(LIB_QT) $(LIB_KDECORE) $(LIB_KDEUI) + +libkomacro_la_METASOURCES = AUTO +SUBDIRS = . +INCLUDES = $(all_includes) diff --git a/kexi/plugins/macros/lib/action.cpp b/kexi/plugins/macros/lib/action.cpp new file mode 100644 index 00000000..e2dc0b64 --- /dev/null +++ b/kexi/plugins/macros/lib/action.cpp @@ -0,0 +1,170 @@ +/*************************************************************************** + * This file is part of the KDE project + * copyright (C) 2005 by Sebastian Sauer (mail@dipe.org) + * copyright (C) 2005 by Tobi Krebs (tobi.krebs@gmail.com) + * copyright (C) 2006 by Sascha Kupper (kusato@kfnv.de) + * + * 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 "action.h" + +#include <kdebug.h> + +using namespace KoMacro; + +namespace KoMacro { + + /** + * @internal d-pointer class to be more flexible on future extension of the + * functionality without to much risk to break the binary compatibility. + */ + class Action::Private + { + public: + + /** + * The name this @a Action has. + */ + QString name; + + /** + * The i18n-caption text this @a Action has. + */ + QString text; + + /** + * The comment the user is able to define for each action. + */ + QString comment; + + /** + * A map of @a Variable instances this @a Action + * provides accessible by there QString name. + */ + Variable::Map varmap; + + /** + * List of variablenames. This list provides a + * sorted order for the @a Variable instances + * defined in the map above. + */ + QStringList varnames; + + }; + +} + +Action::Action(const QString& name, const QString& text) + : QObject() + , KShared() + , d( new Private() ) // create the private d-pointer instance. +{ + kdDebug() << "Action::Action() name=" << name << endl; + d->name = name; + setText(text); + + // Publish this action. + KoMacro::Manager::self()->publishAction( KSharedPtr<Action>(this) ); +} + +Action::~Action() +{ + //kdDebug() << QString("Action::~Action() name=\"%1\"").arg(name()) << endl; + + // destroy the private d-pointer instance. + delete d; +} + +const QString Action::toString() const +{ + return QString("Action:%1").arg(name()); +} + +const QString Action::name() const +{ + return d->name; +} + +void Action::setName(const QString& name) +{ + d->name = name; +} + +const QString Action::text() const +{ + return d->text; +} + +void Action::setText(const QString& text) +{ + d->text = text; +} + +const QString Action::comment() const +{ + return d->comment; +} + +void Action::setComment(const QString& comment) +{ + d->comment = comment; +} + +bool Action::hasVariable(const QString& name) const +{ + return d->varmap.contains(name); +} + +KSharedPtr<Variable> Action::variable(const QString& name) const +{ + return d->varmap.contains(name) ? d->varmap[name] : KSharedPtr<Variable>(0); +} + +Variable::Map Action::variables() const +{ + return d->varmap; +} + +QStringList Action::variableNames() const +{ + return d->varnames; +} + +void Action::setVariable(KSharedPtr<Variable> variable) +{ + const QString name = variable->name(); + if(! d->varmap.contains(name)) { + d->varnames.append(name); + } + d->varmap.replace(name, variable); +} + +void Action::setVariable(const QString& name, const QString& text, const QVariant& variant) +{ + Variable* variable = new Variable(variant); + variable->setName(name); + variable->setText(text); + setVariable( KSharedPtr<Variable>(variable) ); +} + +void Action::removeVariable(const QString& name) +{ + if(d->varmap.contains(name)) { + d->varmap.remove(name); + d->varnames.remove(name); + } +} + +#include "action.moc" diff --git a/kexi/plugins/macros/lib/action.h b/kexi/plugins/macros/lib/action.h new file mode 100644 index 00000000..5200c1a4 --- /dev/null +++ b/kexi/plugins/macros/lib/action.h @@ -0,0 +1,187 @@ +/*************************************************************************** + * This file is part of the KDE project + * copyright (C) 2005 by Sebastian Sauer (mail@dipe.org) + * copyright (C) 2005 by Tobi Krebs (tobi.krebs@gmail.com) + * copyright (C) 2006 by Sascha Kupper (kusato@kfnv.de) + * + * 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 KOMACRO_ACTION_H +#define KOMACRO_ACTION_H + +#include "manager.h" +#include "context.h" +#include "variable.h" + +#include <qobject.h> +#include <ksharedptr.h> +#include <qstringlist.h> + +namespace KoMacro { + + /** + * The Action class extendes KAction to implement some additional + * functionality KAction doesn't provide. + */ + class KOMACRO_EXPORT Action + : public QObject // Qt functionality like signals and slots + , public KShared // shared reference-counting + { + Q_OBJECT + + /// Property to get/set the name. + Q_PROPERTY(QString name READ name WRITE setName) + + /// Property to get/set the text. + Q_PROPERTY(QString text READ text WRITE setText) + + /// Property to get/set the comment. + Q_PROPERTY(QString comment READ comment WRITE setComment) + + public: + + /** + * Shared pointer to implement reference-counting. + */ + typedef QMap<QString, KSharedPtr<Action> > Map; + + /** + * Constructor. + * + * @param name The unique name this @a Action has. + * @param text The i18n-caption text this @a Action has. + */ + explicit Action(const QString& name, const QString& text = QString::null); + + /** + * Destructor. + */ + virtual ~Action(); + + /** + * @return a string representation of the functionality + * this action provides. + */ + virtual const QString toString() const; + + /** + * The name this @a Action has. + */ + const QString name() const; + + /** + * Set the name of the @a Action to @p name . + */ + void setName(const QString& name); + + /** + * @return the i18n-caption text this @a Action has. + */ + const QString text() const; + + /** + * Set the i18n-caption text this @a Action has. + */ + void setText(const QString& text); + + /** + * @return the comment associated with this action. + */ + const QString comment() const; + + /** + * Set the @p comment associated with this action. + */ + void setComment(const QString& comment); + + /** + * @return true if there exists a variable with the + * name @p name else false is returned. + */ + bool hasVariable(const QString& name) const; + + /** + * @return the variable @a Variable defined for the + * name @p name . If there exists no @a Variable with + * such a name, NULL is returned. + */ + KSharedPtr<Variable> variable(const QString& name) const; + + /** + * @return the map of variables this @a Action provides. + */ + Variable::Map variables() const; + + /** + * @return a list of variablenames this @a Action provides.s + */ + QStringList variableNames() const; + + /** + * Append the @a Variable @p variable to list of variables + * this @a Action provides. + */ + void setVariable(KSharedPtr<Variable> variable); + + /** + * Set the variable. + * + * @param name The name the variable should have. + * @param text The i18n-caption used for display. + * @param variant The QVariant value. + */ + void setVariable(const QString& name, const QString& text, const QVariant& variant); + + /** + * Remove the variable defined with @p name . If there exists + * no such variable, nothing is done. + */ + void removeVariable(const QString& name); + + /** + * This function is called, when the @a KoMacro::Variable + * with name @p name used within the @a KoMacro::MacroItem + * @p macroitem got changed. + * + * @param macroitem The @a KoMacro::MacroItem instance where + * the variable defined with @p name is located in. + * @param name The name the @a KoMacro::Variable has. + * @return true if the update was successfully else false + * is returned. + */ + virtual bool notifyUpdated(const KSharedPtr<MacroItem> ¯oitem, const QString& name) { + Q_UNUSED(macroitem); + Q_UNUSED(name); + return true; // The default implementation does nothing. + } + + public slots: + + /** + * Called if the @a Action should be executed within the + * defined @p context . + */ + virtual void activate(KSharedPtr<Context> context) = 0; + + private: + /// @internal d-pointer class. + class Private; + /// @internal d-pointer instance. + Private* const d; + }; + +} + +#endif diff --git a/kexi/plugins/macros/lib/context.cpp b/kexi/plugins/macros/lib/context.cpp new file mode 100644 index 00000000..135c10c9 --- /dev/null +++ b/kexi/plugins/macros/lib/context.cpp @@ -0,0 +1,261 @@ +/*************************************************************************** + * This file is part of the KDE project + * copyright (C) 2005 by Sebastian Sauer (mail@dipe.org) + * copyright (C) 2005 by Tobi Krebs (tobi.krebs@gmail.com) + * + * 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 "context.h" +#include "action.h" +#include "macro.h" +#include "macroitem.h" +#include "exception.h" + +//#include <qtimer.h> +#include <kdebug.h> + +using namespace KoMacro; + +namespace KoMacro { + + /** + * @internal d-pointer class to be more flexible on future extension of the + * functionality without to much risk to break the binary compatibility. + */ + class Context::Private + { + public: + + /** + * The @a Macro instance that owns this @a Context . + */ + KSharedPtr<Macro> macro; + + /** + * List of @a Action instances that are children of the + * macro. + */ + QValueList<KSharedPtr<MacroItem > > items; + + /** + * The currently selected @a MacroItem or NULL if there + * is now @a MacroItem selected yet. + */ + KSharedPtr<MacroItem> macroitem; + + /** + * Map of all @a Variable instance that are defined within + * this context. + */ + QMap<QString, KSharedPtr<Variable > > variables; + + /** + * The @a Exception instance thrown at the last @a activate() + * call or NULL if there was no exception thrown yet. + */ + Exception* exception; + + /// Constructor. + explicit Private(KSharedPtr<Macro> m) + : macro(m) // remember the macro + , items(m->items()) // set d-pointer children to macro children + , exception(0) // no exception yet. + { + } + + /// Destructor. + ~Private() + { + delete exception; + } + }; + +} +//Constructor with initialization of our Private.object (d-pointer) +Context::Context(KSharedPtr<Macro> macro) + : QObject() + , d( new Private(macro) ) // create the private d-pointer instance. +{ +} + +//Destructor. +Context::~Context() +{ + delete d; +} + +//return if we have (d-pointer) variables +bool Context::hasVariable(const QString& name) const +{ + //Use QMap?s contains to check if a variable with name exists + return d->variables.contains(name); +} + +//return variable with name or throw an exception if none is found in variables +KSharedPtr<Variable> Context::variable(const QString& name) const +{ + //Use QMap?s contains to check if a variable with name exists in context + if (d->variables.contains(name)) { + //return it + return d->variables[name]; + } + //if there is a macroitem try to get variable from it + if(d->macroitem.data()) { + KSharedPtr<Variable> v = d->macroitem->variable(name, true); + if(v.data()) { + return v; + } + } + //none found throw exception + throw Exception(QString("Variable name='%1' does not exist.").arg(name)); +} + +//return a map of our (d-pointer) variables +Variable::Map Context::variables() const +{ + return d->variables; +} + +//set a variable +void Context::setVariable(const QString& name, KSharedPtr<Variable> variable) +{ + //debuging infos + kdDebug() << QString("KoMacro::Context::setVariable name='%1' variable='%2'").arg(name).arg(variable->toString()) << endl; + //Use QMap?s replace to set/replace the variable named name + d->variables.replace(name, variable); +} + +//return the associated Macro +KSharedPtr<Macro> Context::macro() const +{ + return d->macro; +} + +//return the currently selected MacroItem +KSharedPtr<MacroItem> Context::macroItem() const +{ + return d->macroitem; +} + +//return if this context had an exception +bool Context::hadException() const +{ + return d->exception != 0; +} + +//return the (d-pointer) exception +Exception* Context::exception() const +{ + return d->exception; +} + +//try to activate all action?s in this context +void Context::activate(QValueList<KSharedPtr<MacroItem > >::ConstIterator it) +{ + //debuging infos + kdDebug() << "Context::activate()" << endl; + //Q_ASSIGN(d->macro); + + //set end to constEnd + QValueList<KSharedPtr<MacroItem > >::ConstIterator end(d->items.constEnd()); + //loop through actions + for(;it != end; ++it) { + // fetch the MacroItem we are currently pointing to. + d->macroitem = KSharedPtr<MacroItem>(*it); + //skip empty macroitems + if(! d->macroitem.data()) { + kdDebug() << "Context::activate() Skipping empty MacroItem" << endl; + continue; + } + + // fetch the Action, the MacroItem points to. + KSharedPtr<Action> action = d->macroitem->action(); + //skip macroitems without an action + if(! action.data()) { + kdDebug() << "Context::activate() Skipping MacroItem with no action" << endl; + continue; + } + + try { + // activate the action + action->activate(this); + } + //catch exceptions + catch(Exception& e) { + //create a new exception from caugth one and set internal exception + d->exception = new Exception(e); + //add new tracemessages + //the macro name + d->exception->addTraceMessage( QString("macro=%1").arg(d->macro->name()) ); + //the action name + d->exception->addTraceMessage( QString("action=%1").arg(action->name()) ); + //and all variables wich belong to the action/macro + QStringList variables = action->variableNames(); + for(QStringList::Iterator vit = variables.begin(); vit != variables.end(); ++vit) { + KSharedPtr<Variable> v = d->macroitem->variable(*vit, true); + d->exception->addTraceMessage( QString("%1=%2").arg(*vit).arg(v->toString()) ); + } + return; // abort execution + } + } + + // The run is done. So, let's remove the currently selected item to + // outline, that we did the job and there stays no dangling item. + d->macroitem = KSharedPtr<MacroItem>(0); +} + +//try to activated an context +void Context::activate(KSharedPtr<Context> context) +{ + //setup context + delete d->exception; d->exception = 0; + + if(context->hadException()) { + // if the context in which this context should run in already had an exception, + // we adopt this exception and abort the execution. + d->exception = new Exception( *context->exception() ); + return; + } + + // Merge the passed context into this context + Variable::Map variables = context->variables(); + //copy variables + Variable::Map::ConstIterator it, end( variables.constEnd() ); + for( it = variables.constBegin(); it != end; ++it) + setVariable(it.key(), it.data()); + + //activate copied context. + activate(d->items.constBegin()); +} + +//try to continue activation of a context +void Context::activateNext() +{ + //setup/clear context, + //allows us to continue activation even when an exception happend before + delete d->exception; d->exception = 0; + + if(! d->macroitem) { // if no MacroItem is defined, we don't need to try to continue execution + return; + } + + //find the macroitem from which to continue + QValueList<KSharedPtr<MacroItem > >::ConstIterator it = d->items.find(d->macroitem); + if (it != d->items.constEnd()) { + activate(++it); // try to continue the execution. + } +} + +#include "context.moc" diff --git a/kexi/plugins/macros/lib/context.h b/kexi/plugins/macros/lib/context.h new file mode 100644 index 00000000..dd467dad --- /dev/null +++ b/kexi/plugins/macros/lib/context.h @@ -0,0 +1,141 @@ +/*************************************************************************** + * This file is part of the KDE project + * copyright (C) 2005 by Sebastian Sauer (mail@dipe.org) + * copyright (C) 2005 by Tobi Krebs (tobi.krebs@gmail.com) + * + * 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 KOMACRO_CONTEXT_H +#define KOMACRO_CONTEXT_H + +#include <qobject.h> +#include <ksharedptr.h> + +#include "variable.h" + +namespace KoMacro { + + // Forward declaration. + class Macro; + class MacroItem; + class Action; + class Exception; + + /** + * The context of an execution. If a @a Macro got executed it creates + * an instance of this class and passes it around all it's children + * as local execution context. + */ + class KOMACRO_EXPORT Context + : public QObject + , public KShared + { + Q_OBJECT + public: + + /** + * Constructor. + * + * @param macro The @a Macro this @a Context belongs to. + */ + explicit Context(KSharedPtr<Macro> macro); + + /** + * Destructor. + */ + ~Context(); + + /** + * @return true if there exists a variable with name @p name + * else false got returned. + */ + bool hasVariable(const QString& name) const; + + /** + * @return the @a Variable defined with name @p name or + * NULL if there exists no such variable. + */ + KSharedPtr<Variable> variable(const QString& name) const; + + /** + * @return a map of all @a Variable instance that are defined + * within this context. + */ + Variable::Map variables() const; + + /** + * Set the variable @p variable defined with name @p name . If + * there exists already a variable with that name replace it. + */ + void setVariable(const QString& name, KSharedPtr<Variable> variable); + + /** + * @return the associated macro + */ + KSharedPtr<Macro> macro() const; + + /** + * @return the currently selected @a MacroItem instance + * or NULL if there is no @a MacroItem selected yet. + */ + KSharedPtr<MacroItem> macroItem() const; + + /** + * @return true if the last @a activate() stopped with an + * exception else false is returned. + */ + bool hadException() const; + + /** + * @return the @a Exception instance that was thrown on + * the last call of @a activate() . If there was no + * exception NULL is returned. + */ + Exception* exception() const; + + private slots: + + /** + * A @a Context does take care of an execution-chain which + * should be activated one after another. The @a Context + * remembers what @a Action should be executed next and + * calling this slot just activates those @a Action . + */ + virtual void activate(QValueList<KSharedPtr <MacroItem> >::ConstIterator it); + + public slots: + + /** + * This slot extends the slot above with the passed + * @a Context @p context which will be used as + * parent context for this context. + */ + virtual void activate(KSharedPtr<Context> context); + + /** + * This slot continues execution. + */ + virtual void activateNext(); + + private: + /// @internal d-pointer class. + class Private; + /// @internal d-pointer instance. + Private* const d; + }; + +} + +#endif diff --git a/kexi/plugins/macros/lib/exception.cpp b/kexi/plugins/macros/lib/exception.cpp new file mode 100644 index 00000000..7cfc7d71 --- /dev/null +++ b/kexi/plugins/macros/lib/exception.cpp @@ -0,0 +1,97 @@ +/*************************************************************************** + * This file is part of the KDE project + * copyright (C) 2005 by Sebastian Sauer (mail@dipe.org) + * copyright (C) 2005 by Tobi Krebs (tobi.krebs@gmail.com) + * + * 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 "exception.h" + +#include <kdebug.h> + +using namespace KoMacro; + +namespace KoMacro { + + /** + * @internal d-pointer class to be more flexible on future extension of the + * functionality without to much risk to break the binary compatibility. + */ + class Exception::Private + { + public: + + /// A describing errormessage. + const QString errormessage; + + /// A more detailed list of tracemessages. + QString tracemessages; + + /** + * Constructor. + */ + Private(const QString& errormessage) + : errormessage(errormessage) + { + } + + }; + +} + +//constructor +Exception::Exception(const QString& errormessage) + : d( new Private(errormessage) ) // create the private d-pointer instance. +{ + //debuging infos + kdDebug() << QString("Exception errormessage=\"%1\"").arg(errormessage) << endl; +} + +//copy constructor +Exception::Exception (const Exception& e) + : d( new Private( e.errorMessage() ) ) +{ + d->tracemessages = e.traceMessages(); +} + +//deconstructor +Exception::~Exception() +{ + delete d; +} + +//get d-pointer errormessage +const QString Exception::errorMessage() const +{ + return d->errormessage; +} + +//get d-pointer tracemessages +const QString Exception::traceMessages() const +{ + return d->tracemessages; +} + +//add a Qstring to d-pointer tracemessages +void Exception::addTraceMessage(const QString& tracemessage) +{ + //no tracemessages till now + if(d->tracemessages.isEmpty()) + d->tracemessages = tracemessage; + //append to existing ones + else + d->tracemessages += "\n" + tracemessage; +} + diff --git a/kexi/plugins/macros/lib/exception.h b/kexi/plugins/macros/lib/exception.h new file mode 100644 index 00000000..73504de0 --- /dev/null +++ b/kexi/plugins/macros/lib/exception.h @@ -0,0 +1,84 @@ +/*************************************************************************** + * This file is part of the KDE project + * copyright (C) 2005 by Sebastian Sauer (mail@dipe.org) + * copyright (C) 2005 by Tobi Krebs (tobi.krebs@gmail.com) + * + * 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 KOMACRO_EXCEPTION_H +#define KOMACRO_EXCEPTION_H + +#include <qstring.h> +#include <qstringlist.h> + +#include "komacro_export.h" + +namespace KoMacro { + + /** + * Base Exception class. All exceptions we like to use within KoMacro + * need to inheritate from this exception. + */ + class KOMACRO_EXPORT Exception + { + public: + + /** + * Constructor. + * + * @param errormessage A describing errormessage why the + * exception got thrown. + */ + explicit Exception(const QString& errormessage); + + /** + * Copy-constructor. + */ + Exception(const Exception&); + + /** + * Destructor. + */ + virtual ~Exception(); + + /** + * @return a describing errormessage. + */ + const QString errorMessage() const; + + /** + * @return a stringlist of traces. This are normaly just + * simple strings to show the way the exception was gone + * from bottom-up where the error was thrown till where + * we finally catched the error to display it to the + * user. + */ + const QString traceMessages() const; + + /** + * Add the message @p tracemessage to the list of traces. + */ + void addTraceMessage(const QString& tracemessage); + + private: + /// @internal d-pointer class. + class Private; + /// @internal d-pointer instance. + Private* const d; + }; + +} + +#endif diff --git a/kexi/plugins/macros/lib/komacro_export.h b/kexi/plugins/macros/lib/komacro_export.h new file mode 100644 index 00000000..cc4b41a8 --- /dev/null +++ b/kexi/plugins/macros/lib/komacro_export.h @@ -0,0 +1,39 @@ +/*************************************************************************** + * This file is part of the KDE project + * copyright (C) 2006 by Sebastian Sauer (mail@dipe.org) + * copyright (C) 2006 by Tobi Krebs (tobi.krebs@gmail.com) + * + * 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 KOMACRO_EXPORT_H_ +#define KOMACRO_EXPORT_H_ + +#ifdef __cplusplus +# include <kdeversion.h> /* this will also include <kdelibs_export.h>, if available */ +#endif + +/* KDE_EXPORT will be defined multiple times without this on kdelibs 3.3 (tested on 3.3.1) */ +#include <kdemacros.h> + +/* workaround for KDElibs < 3.2 on !win32 */ +#ifndef KDE_EXPORT +# define KDE_EXPORT +#endif + +#ifndef KOMACRO_EXPORT +# define KOMACRO_EXPORT KDE_EXPORT +#endif + +#endif diff --git a/kexi/plugins/macros/lib/macro.cpp b/kexi/plugins/macros/lib/macro.cpp new file mode 100644 index 00000000..688cc7b0 --- /dev/null +++ b/kexi/plugins/macros/lib/macro.cpp @@ -0,0 +1,126 @@ +/*************************************************************************** + * This file is part of the KDE project + * copyright (C) 2005 by Sebastian Sauer (mail@dipe.org) + * copyright (C) 2005 by Tobi Krebs (tobi.krebs@gmail.com) + * + * 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 "macro.h" +#include "macroitem.h" +#include "manager.h" +#include "context.h" +#include "variable.h" + +#include <qdom.h> +#include <kdebug.h> + +using namespace KoMacro; + +namespace KoMacro { + + /** + * @internal d-pointer class to be more flexible on future extension of the + * functionality without to much risk to break the binary compatibility. + */ + class Macro::Private + { + public: + + /** + * A list of @a MacroItem instances. + */ + QValueList<KSharedPtr<MacroItem > > itemlist; + + /** + * The name the @a Macro has. + */ + QString name; + + }; + +} + +//constructor, initalize internal (d-pointer) name +Macro::Macro(const QString& name) + : QObject() + , KShared() + , XMLHandler(this) + , d( new Private() ) // create the private d-pointer instance. +{ + d->name = name; +} + +//destructor +Macro::~Macro() +{ + // destroy the private d-pointer instance. + delete d; +} + +//get internal (d-pointer) name +const QString Macro::name() const +{ + return d->name; +} + +//set internal (d-pointer) name +void Macro::setName(const QString& name) +{ + d->name = name; +} + +//get an "extended" name +const QString Macro::toString() const +{ + return QString("Macro:%1").arg(name()); +} + +//get (d-pointer) itemlist +QValueList<KSharedPtr<MacroItem > >& Macro::items() const +{ + return d->itemlist; +} + +//add a macroitem to internal (d-pointer) itemlist +void Macro::addItem(KSharedPtr<MacroItem> item) +{ + d->itemlist.append(item); +} +//clear internal (d-pointer) itemlist +void Macro::clearItems() +{ + d->itemlist.clear(); +} + +//run our macro +KSharedPtr<Context> Macro::execute(QObject* sender) +{ + kdDebug() << "Macro::execute(KSharedPtr<Context>)" << endl; + + //create context in which macro can/should run + KSharedPtr<Context> c = KSharedPtr<Context>( new Context(this) ); + if(sender) { + // set the sender-variable if we got a sender QObject. + c->setVariable("[sender]", KSharedPtr<Variable>( new Variable(sender) )); + } + //connect(context, SIGNAL(activated()), this, SIGNAL(activated())); + + //call activate in the context of the macro + c->activate( c ); + + return c; +} + +#include "macro.moc" diff --git a/kexi/plugins/macros/lib/macro.h b/kexi/plugins/macros/lib/macro.h new file mode 100644 index 00000000..da38e05b --- /dev/null +++ b/kexi/plugins/macros/lib/macro.h @@ -0,0 +1,130 @@ +/*************************************************************************** + * This file is part of the KDE project + * copyright (C) 2005 by Sebastian Sauer (mail@dipe.org) + * copyright (C) 2005 by Tobi Krebs (tobi.krebs@gmail.com) + * + * 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 KOMACRO_MACRO_H +#define KOMACRO_MACRO_H + +#include <qobject.h> +#include <ksharedptr.h> + +#include "action.h" +#include "xmlhandler.h" + +namespace KoMacro { + + // Forward declarations. + class Manager; + class MacroItem; + class Context; + + /** + * The Macro class implements all the action-handling. Internaly the + * Macro provides a collection of @a MacroItem instances which each + * of them points to an @a Action instance. + */ + class KOMACRO_EXPORT Macro + : public QObject // Qt functionality like signals and slots + , public KShared // shared reference-counting + , public XMLHandler // to (un-)serialize from/to XML + { + Q_OBJECT + + public: + + /** + * A QMap of @a Macro instances accessible by there unique name. Each + * class should use this typemap rather then the QMap direct. That + * way we are more flexible on future changes. + */ + typedef QMap<QString, KSharedPtr<Macro > > Map; + + /** + * Constructor. + * + * @param name The internal name this @a Macro has. This + * name will be used as unique identifier. + */ + explicit Macro(const QString& name); + + /** + * Destructor. + */ + virtual ~Macro(); + + /** + * @return the name this @a Macro instance has. + */ + const QString name() const; + + /** + * Set the @p name this @a Macro instance has. + */ + void setName(const QString& name); + + /** + * @return a string-representation of the macro. + */ + virtual const QString toString() const; + + /** + * @return a list of @a MacroItem instances which + * are children of this @a Macro . + */ + QValueList< KSharedPtr<MacroItem> >& items() const; + + /** + * Add the @a MacroItem @p item to the list of items + * this @a Macro has. + */ + void addItem(KSharedPtr<MacroItem> item); + + /** + * Removes all @a MacroItem instances this @a Macro has. + */ + void clearItems(); + + /** + * Connect the Qt signal @p signal of the QObject @p sender + * with this @a Macro . If the signal got emitted this + * @a Macro instance will be activated and the in the + * signal passed arguments are transfered into the + * activation @a Context . + */ + //void connectSignal(const QObject* sender, const char* signal); + + public slots: + + /** + * Called if the @a Macro should be executed. + * + * @param context The @a Context this @a Macro should + * be executed in. + */ + virtual KSharedPtr<Context> execute(QObject* sender); + + private: + /// @internal d-pointer class. + class Private; + /// @internal d-pointer instance. + Private* const d; + }; + +} + +#endif diff --git a/kexi/plugins/macros/lib/macroitem.cpp b/kexi/plugins/macros/lib/macroitem.cpp new file mode 100644 index 00000000..4027f2cc --- /dev/null +++ b/kexi/plugins/macros/lib/macroitem.cpp @@ -0,0 +1,217 @@ +/*************************************************************************** + * This file is part of the KDE project + * copyright (C) 2006 by Sebastian Sauer (mail@dipe.org) + * copyright (C) 2006 by Sascha Kupper (kusato@kfnv.de) + * + * 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 "macroitem.h" + +#include <kdebug.h> + +using namespace KoMacro; + +namespace KoMacro { + + /** + * @internal d-pointer class to be more flexible on future extension of the + * functionality without to much risk to break the binary compatibility. + */ + class MacroItem::Private + { + public: + /** + * The @a Action this @a MacroItem has. + */ + KSharedPtr<Action> action; + + /** + * The comment this @a MacroItem has. + */ + QString comment; + + /** + * The @a QMap of @a Variable this @a MacroItem has. + */ + Variable::Map variables; + + /** + * define a @a QVariant -cast as inline for better performance + * @return the casted @a QVariant by passing a @param variant and its + * expected QVariant::Type @param type. + */ + inline const QVariant cast(const QVariant& variant, QVariant::Type type) const + { + // If ok is true the QVariant v holds our new and to the correct type + // casted variant value. If ok is false the as argument passed variant + // QVariant contains the (maybe uncasted string to prevent data-loosing + // what would happen if we e.g. would expect an integer and cast it to + // an incompatible non-int string) value. + bool ok = false; + QVariant v; + + // Try to cast the passed variant to the expected variant-type. + switch(type) { + case QVariant::Bool: { + const QString s = variant.toString(); + ok = (s == "true" || s == "false" || s == "0" || s == "1" || s == "-1"); + v = QVariant( variant.toBool(), 0 ); + } break; + case QVariant::Int: { + v = variant.toInt(&ok); + // Check if the cast is correct. + Q_ASSERT(!ok || v.toString() == variant.toString()); + } break; + case QVariant::UInt: { + v = variant.toUInt(&ok); + Q_ASSERT(!ok || v.toString() == variant.toString()); + } break; + case QVariant::LongLong: { + v = variant.toLongLong(&ok); + Q_ASSERT(!ok || v.toString() == variant.toString()); + } break; + case QVariant::ULongLong: { + v = variant.toULongLong(&ok); + Q_ASSERT(!ok || v.toString() == variant.toString()); + } break; + case QVariant::Double: { + v = variant.toDouble(&ok); + Q_ASSERT(!ok || v.toString() == variant.toString()); + } break; + case QVariant::String: { + ok = true; // cast will always be successfully + v = variant.toString(); + } break; + default: { + // If we got another type we try to let Qt handle it... + ok = v.cast(type); + kdWarning()<<"MacroItem::Private::cast() Unhandled ok="<<ok<<" type="<<type<<" value="<<v<<endl; + } break; + } + + return ok ? v : variant; + } + + }; + +} + +MacroItem::MacroItem() + : KShared() + , d( new Private() ) +{ +} + +MacroItem::~MacroItem() +{ + delete d; +} + +QString MacroItem::comment() const +{ + return d->comment; +} + +void MacroItem::setComment(const QString& comment) +{ + d->comment = comment; +} + +KSharedPtr<Action> MacroItem::action() const +{ + return d->action; +} + +void MacroItem::setAction(KSharedPtr<Action> action) +{ + d->action = action; +} + +QVariant MacroItem::variant(const QString& name, bool checkaction) const +{ + KSharedPtr<Variable> v = variable(name, checkaction); + return v.data() ? v->variant() : QVariant(); +} + +KSharedPtr<Variable> MacroItem::variable(const QString& name, bool checkaction) const +{ + if(d->variables.contains(name)) + return d->variables[name]; + if(checkaction && d->action.data()) + return d->action->variable(name); + return KSharedPtr<Variable>(0); +} + +Variable::Map MacroItem::variables() const +{ + return d->variables; +} + +bool MacroItem::setVariant(const QString& name, const QVariant& variant) +{ + // Let's look if there is an action defined for the variable. If that's + // the case, we try to use that action to preserve the type of the variant. + KSharedPtr<Variable> actionvariable = d->action ? d->action->variable(name) : KSharedPtr<Variable>(0); + + // If we know the expected type, we try to cast the variant to the expected + // type else the variant stays untouched (so, it will stay a string). + const QVariant v = actionvariable.data() + ? d->cast(variant, actionvariable->variant().type()) // try to cast the variant + : variant; // don't cast anything, just leave the string-type... + + // Now let's try to determinate the variable which should be changed. + KSharedPtr<Variable> variable = d->variables[name]; + if(! variable.data()) { + // if there exists no such variable yet, create one. + kdDebug() << "MacroItem::setVariable() Creating new variable name=" << name << endl; + + variable = KSharedPtr<Variable>( new Variable() ); + variable->setName(name); + d->variables.replace(name, variable); + } + + // Remember the previous value for the case we like to restore it. + const QVariant oldvar = variable->variant(); + + // Set the variable. + variable->setVariant(v); + + // Now we inform the referenced action that a variable changed. If + // notifyUpdated() returns false, the action rejects the new variable + // and we need to restore the previous value. + if(d->action && ! d->action->notifyUpdated(this, name)) { + kdWarning() << "MacroItem::setVariable() Notify failed for variable name=" << name << endl; + variable->setVariant(oldvar); + return false; // the action rejected the changed variable whyever... + } + + // Job done successfully. The variable is changed to the new value. + return true; +} + +KSharedPtr<Variable> MacroItem::addVariable(const QString& name, const QVariant& variant) +{ + Q_ASSERT(! d->variables.contains(name) ); + // Create a new Variable. + KSharedPtr<Variable> variable = KSharedPtr<Variable>( new Variable() ); + variable->setName(name); + + // Put it into the Variable-map. + d->variables.replace(name, variable); + + // Set the variant of the Variable. + this->setVariant(name, variant); + return variable; +} diff --git a/kexi/plugins/macros/lib/macroitem.h b/kexi/plugins/macros/lib/macroitem.h new file mode 100644 index 00000000..8f3e1502 --- /dev/null +++ b/kexi/plugins/macros/lib/macroitem.h @@ -0,0 +1,142 @@ +/*************************************************************************** + * This file is part of the KDE project + * copyright (C) 2006 by Sebastian Sauer (mail@dipe.org) + * copyright (C) 2006 by Sascha Kupper (kusato@kfnv.de) + * + * 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 KOMACRO_MACROITEM_H +#define KOMACRO_MACROITEM_H + +#include <qobject.h> + +#include <ksharedptr.h> + +// Forward declarations. +class QDomElement; + +#include "action.h" +#include "context.h" + +namespace KoMacro { + + // Forward-declarations. + //class Action; + + /** + * The MacroItem class is an item in a @a Macro and represents one + * single execution step. Each MacroItem points to 0..1 @a Action + * instances which implement the execution. So, the MacroItem provides + * a simple state-pattern on the one hand (depending on the for this + * MacroItem choosen @a Action implementation) and holds the by the + * user defined modifications like e.g. the comment on the other hand. + */ + class KOMACRO_EXPORT MacroItem : public KShared + { + + public: + + /** + * A list of \a MacroItem instances. + */ + typedef QValueList<KSharedPtr<MacroItem > > List; + + /** + * Constructor. + */ + explicit MacroItem(); + + /** + * Destructor. + */ + ~MacroItem(); + + /** + * @return the comment defined by the user for + * this @a MacroItem . + */ + QString comment() const; + + /** + * Set the comment @param comment defined by the user for this + * @a MacroItem . + */ + void setComment(const QString& comment); + + /** + * @return the @a Action this @a MacroItem points + * to. This method will return NULL if there is + * no @a Action defined yet else the returned + * @a Action will be used to implement the execution. + */ + KSharedPtr<Action> action() const; + + /** + * Set the @a Action @param action this @a MacroItem points to. + */ + void setAction(KSharedPtr<Action> action); + + /** + * @return @a Variant from the @a Variable identified with + * the name @param name . If this @a MacroItem doesn't + * have a @a Variable with that name NULL is + * returned. + * If the boolean value @param checkaction is true, we + * also look if our @a Action may know about + * such a @param name in the case this @a MacroItem + * doesn't have such a name. + */ + QVariant variant(const QString& name, bool checkaction = false) const; + + /** + * @return the @a Variable instance identified with + * the name @param name . If this @a MacroItem doesn't + * have a @a Variable with that name NULL is + * returned. + * If the boolean value @param checkaction is true, we + * also look if our @a Action may know about + * such a @param name in the case this @a MacroItem + * doesn't have such a name. + */ + KSharedPtr<Variable> variable(const QString& name, bool checkaction = false) const; + + /** + * @return a map of @a Variable instances. + */ + QMap<QString, KSharedPtr<Variable> > variables() const; + + /** + * Set the @a QVariant @param variant as variable with the variablename + * @param name . + * @return a bool for successfull setting. + */ + bool setVariant(const QString& name, const QVariant& variant); + + /** + * Add a new variable with the vaiablename @param name and the given + * @a QVariant @param variant to our @a MacroItem instance. + */ + KSharedPtr<Variable> addVariable(const QString& name, const QVariant& variant); + + private: + /// @internal d-pointer class. + class Private; + /// @internal d-pointer instance. + Private* const d; + }; + +} + +#endif diff --git a/kexi/plugins/macros/lib/manager.cpp b/kexi/plugins/macros/lib/manager.cpp new file mode 100644 index 00000000..77ad98b1 --- /dev/null +++ b/kexi/plugins/macros/lib/manager.cpp @@ -0,0 +1,170 @@ +/*************************************************************************** + * This file is part of the KDE project + * copyright (C) 2005 by Sebastian Sauer (mail@dipe.org) + * copyright (C) 2005 by Tobi Krebs (tobi.krebs@gmail.com) + * + * 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 "manager.h" +#include "action.h" +#include "function.h" +#include "macro.h" +#include "exception.h" + +#include <qobject.h> +#include <qwidget.h> +#include <qdom.h> +#include <kxmlguibuilder.h> +#include <kdebug.h> + +using namespace KoMacro; + +namespace KoMacro { + + /** + * @internal d-pointer class to be more flexible on future extension of the + * functionality without to much risk to break the binary compatibility. + */ + class Manager::Private + { + public: + KXMLGUIClient* const xmlguiclient; + QMap<QString, KSharedPtr<Macro > > macros; + + QStringList actionnames; + QMap<QString, KSharedPtr<Action> > actions; + + QMap<QString, QGuardedPtr<QObject> > objects; + + Private(KXMLGUIClient* const xmlguiclient) + : xmlguiclient(xmlguiclient) + { + } + }; + + /// Pointer to our static singleton. + static ::KoMacro::Manager* _self = 0; + + /// Automatically deletes our singleton on termination. + static KStaticDeleter< ::KoMacro::Manager > _manager; + +} + +void Manager::init(KXMLGUIClient* xmlguiclient) +{ + if(! _self) { + ::KoMacro::Manager* manager = new ::KoMacro::Manager(xmlguiclient); + _manager.setObject(_self, manager); + } + else { + throw Exception("Already initialized."); + } +} + +Manager* Manager::self() +{ + //Q_ASSERT(_self); + return _self; +} + +Manager::Manager(KXMLGUIClient* const xmlguiclient) + : d( new Private(xmlguiclient) ) // create the private d-pointer instance. +{ + kdDebug() << "Manager::Manager() Ctor" << endl; + QObject* obj = dynamic_cast<QObject*>(xmlguiclient); + if(obj) { + d->objects.replace(obj->name(), obj); + } + + //TESTCASE + d->objects.replace("TestCase", new QWidget()); +} + +Manager::~Manager() +{ + // destroy the private d-pointer instance. + delete d; +} + +KXMLGUIClient* Manager::guiClient() const +{ + return d->xmlguiclient; +} + +bool Manager::hasMacro(const QString& macroname) +{ + return d->macros.contains(macroname); +} + +KSharedPtr<Macro> Manager::getMacro(const QString& macroname) +{ + return d->macros[macroname]; +} + +void Manager::addMacro(const QString& macroname, KSharedPtr<Macro> macro) +{ + d->macros.replace(macroname, macro); +} + +void Manager::removeMacro(const QString& macroname) +{ + d->macros.remove(macroname); +} + +KSharedPtr<Macro> Manager::createMacro(const QString& macroname) +{ + KSharedPtr<Macro> macro = KSharedPtr<Macro>( new Macro(macroname) ); + return macro; +} + +KSharedPtr<Action> Manager::action(const QString& name) const +{ + return d->actions[name]; +} + +Action::Map Manager::actions() const +{ + return d->actions; +} + +QStringList Manager::actionNames() const +{ + return d->actionnames; +} + +void Manager::publishAction(KSharedPtr<Action> action) +{ + const QString name = action->name(); + if(! d->actions.contains(name)) { + d->actionnames.append(name); + } + d->actions.replace(name, action); +} + +void Manager::publishObject(const QString& name, QObject* object) +{ + Q_ASSERT(! d->objects.contains(name)); + d->objects.replace(name, object); +} + +QGuardedPtr<QObject> Manager::object(const QString& name) const +{ + return d->objects[name]; +} + +QMap<QString, QGuardedPtr<QObject> > Manager::objects() const +{ + return d->objects; +} diff --git a/kexi/plugins/macros/lib/manager.h b/kexi/plugins/macros/lib/manager.h new file mode 100644 index 00000000..964f9d7c --- /dev/null +++ b/kexi/plugins/macros/lib/manager.h @@ -0,0 +1,219 @@ +/*************************************************************************** + * This file is part of the KDE project + * copyright (C) 2005 by Sebastian Sauer (mail@dipe.org) + * copyright (C) 2005 by Tobi Krebs (tobi.krebs@gmail.com) + * + * 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 KOMACRO_MANAGER_H +#define KOMACRO_MANAGER_H + +#include <qmap.h> +#include <qguardedptr.h> +#include <ksharedptr.h> +#include <kxmlguiclient.h> +#include <kstaticdeleter.h> + +#include "komacro_export.h" + +class QObject; +class QDomElement; + +namespace KoMacro { + + // Forward declarations. + class Action; + class Macro; + + /** + * The Manager class acts as window-wide manager for macros. + * + * Example how KoMacro could be used. + * @code + * // We have a class that inheritates from QObject and + * // implements some public signals and slots that will + * // be accessible by Macros once a class-instance + * // got published. + * class PublishedObject : public QObject {}; + * + * // Somewhere we have our KMainWindow. + * KMainWindow* mainwindow = new KMainWindow(); + * + * // Create a new KoMacro::Manager instance to access the + * // Macro-framework. + * KoMacro::Manager* manager = new KoMacro::Manager( mainwindow ); + * + * // Now we like to publish a QObject + * PublishedObject* publishedobject = new PublishedObject(); + * manager->publishObject(publishedobject); + * + * // ... here we are able to use manager->createAction() to + * // create Action instances on the fly and work with them. + * + * // Finally free the publishedobject instance we created. We + * // need to free it manualy cause PublishedObject doesn't + * // got a QObject parent as argument. + * delete publishedobject; + * + * // Finally free the manager-instance. It's always needed + * // to free the instance by yourself! + * delete manager; + * @endcode + */ + class KOMACRO_EXPORT Manager + { + friend class KStaticDeleter< ::KoMacro::Manager >; + private: + + /** + * Constructor. + * + * @param xmlguiclient The KXMLGUIClient instance this + * @a Manager is associated with. + */ + explicit Manager(KXMLGUIClient* const xmlguiclient); + + /** + * Destructor. + */ + virtual ~Manager(); + + public: + + /** + * Initialize this \a Manager singleton. This function + * needs to be called exactly once to initialize the + * \a Manager singleton before \a self() got used. + */ + static void init(KXMLGUIClient* xmlguiclient); + + /** + * @return a pointer to a Manager singleton-instance. The + * static method \a init() needs to be called exactly once + * before calling this method else we may return NULL . + */ + static Manager* self(); + + /** + * @return the KXMLGUIClient instance this @a Manager is + * associated with. + */ + KXMLGUIClient* guiClient() const; + + /** + * \return true if we carry a \a Macro with the + * defined \p macroname . + */ + bool hasMacro(const QString& macroname); + + /** + * \return the \a Macro defined with \p macroname + * or NULL if we don't have such a \a Macro. + */ + KSharedPtr<Macro> getMacro(const QString& macroname); + + /** + * Add a new \a Macro to the list of known macros. If + * there exists already a \a Macro instance with the + * defined \p macroname then the already existing one + * will be replace. + * + * \param macroname The name the \a Macro will be + * accessible as. + * \param macro The \a Macro instance. + */ + void addMacro(const QString& macroname, KSharedPtr<Macro> macro); + + /** + * Remove the \a Macro defined with \p macroname . If + * we don't know about a \a Macro with that \p macroname + * nothing happens. + */ + void removeMacro(const QString& macroname); + + /** + * Factory function to create a new \a Macro instances. + * The returned new \a Macro instance will not be added + * to the list of known macros. Use \a addMacro if you + * like to attach the returned new \a Macro to this + * \a Manager instance. + */ + KSharedPtr<Macro> createMacro(const QString& macroname); + +#if 0 + /** + * Factory method to create @a Action instances from the + * defined @p element . + * + * @param element The serialized QDomElement that should + * be used to create the @a Action instance. + * @return A new @a Action instance or NULL if the + * defined @p element is not valid. + * + * @deprecated Moved to common XMLReader/XMLWriter classes. Use Macro::xmlHandler() ! + */ + KSharedPtr<Action> createAction(const QDomElement& element); +#endif + + /** + * @return the @a Action which was published under the + * name @p name or returns an empty @a KSharedPtr<Action> object + * if there was no such @a Action published. + */ + KSharedPtr<Action> action(const QString& name) const; + + /** + * @return a map of all published actions. + */ + QMap<QString, KSharedPtr<Action> > actions() const; + + /** + * @return a list of all published actions. + */ + QStringList actionNames() const; + + /** + * Publish the @a Action @p action . The published @a Action + * will be accessible via it's unique name. + */ + void publishAction(KSharedPtr<Action> action); + + /** + * Publish the passed QObject @p object. Those object will + * provide it's slots as callable functions. + */ + void publishObject(const QString& name, QObject* object); + + /** + * @return the publish QObject defined with name @p name + * or NULL if there exists no such object. + */ + QGuardedPtr<QObject> object(const QString& name) const; + + /** + * @return a map of the published QObject instances. + */ + QMap<QString, QGuardedPtr<QObject> > objects() const; + + private: + /// @internal d-pointer class. + class Private; + /// @internal d-pointer instance. + Private* const d; + }; + +} + +#endif diff --git a/kexi/plugins/macros/lib/metamethod.cpp b/kexi/plugins/macros/lib/metamethod.cpp new file mode 100644 index 00000000..8aa4dc54 --- /dev/null +++ b/kexi/plugins/macros/lib/metamethod.cpp @@ -0,0 +1,344 @@ +/*************************************************************************** + * This file is part of the KDE project + * copyright (C) 2005 by Sebastian Sauer (mail@dipe.org) + * copyright (C) 2005 by Tobi Krebs (tobi.krebs@gmail.com) + * + * 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 "metamethod.h" +#include "metaobject.h" +#include "metaparameter.h" +#include "variable.h" +#include "exception.h" + +#include <qobject.h> +#include <qmetaobject.h> + +// to access the Qt3 QUObject API. +#include <private/qucom_p.h> +#include <private/qucomextra_p.h> + +#include <kdebug.h> + +using namespace KoMacro; + +namespace KoMacro { + + /** + * @internal d-pointer class to be more flexible on future extension of the + * functionality without to much risk to break the binary compatibility. + */ + class MetaMethod::Private + { + public: + + /** + * The signature this @a MetaMethod has. + */ + QString signature; + + /** + * The signature tagname this @a MetaMethod has. + */ + QString signaturetag; + + /** + * The signature arguments this @a MetaMethod has. + */ + QString signaturearguments; + + /** + * Cached signature arguments parsed into a list + * of @a MetaParameter instances. + */ + MetaParameter::List arguments; + + /** + * The @a MetaObject this @a MetaMethod belongs to or is NULL + * if this @a MetaMethod doesn't belong to any @a MetaObject + * yet. + */ + KSharedPtr<MetaObject> object; + + /** + * The @a MetaMethod::Type this method provides access + * to. + */ + MetaMethod::Type type; + }; + +} + +MetaMethod::MetaMethod(const QString& signature, Type type, KSharedPtr<MetaObject> object) + : KShared() + , d( new Private() ) // create the private d-pointer instance. +{ + d->signature = signature; + d->object = object; + d->type = type; + + int startpos = d->signature.find("("); + int endpos = d->signature.findRev(")"); + if(startpos < 0 || startpos > endpos) { + throw Exception(QString("Invalid signature \"%1\"").arg(d->signature)); + } + + d->signaturetag = d->signature.left(startpos).stripWhiteSpace(); + if(d->signaturetag.isEmpty()) { + throw Exception(QString("Invalid tagname in signature \"%1\"").arg(d->signature)); + } + + d->signaturearguments = d->signature.mid(startpos + 1, endpos - startpos - 1).stripWhiteSpace(); + + do { + int commapos = d->signaturearguments.find(","); + int starttemplatepos = d->signaturearguments.find("<"); + if(starttemplatepos >= 0 && (commapos < 0 || starttemplatepos < commapos)) { + int endtemplatepos = d->signaturearguments.find(">", starttemplatepos); + if(endtemplatepos <= 0) { + throw Exception(QString("No closing template-definiton in signature \"%1\"").arg(d->signature)); + } + commapos = d->signaturearguments.find(",", endtemplatepos); + } + + if(commapos > 0) { + QString s = d->signaturearguments.left(commapos).stripWhiteSpace(); + if(! s.isEmpty()) { + d->arguments.append( new MetaParameter(s) ); + } + d->signaturearguments = d->signaturearguments.right(d->signaturearguments.length() - commapos - 1); + } + else { + QString s = d->signaturearguments.stripWhiteSpace(); + if(! s.isEmpty()) { + d->arguments.append( new MetaParameter(s) ); + } + break; + } + } while(true); +} + +MetaMethod::~MetaMethod() +{ + delete d; +} + +KSharedPtr<MetaObject> const MetaMethod::object() const +{ + return d->object; +} + +const QString MetaMethod::signature() const +{ + return d->signature; +} + +const QString MetaMethod::signatureTag() const +{ + return d->signaturetag; +} + +const QString MetaMethod::signatureArguments() const +{ + return d->signaturearguments; +} + +MetaMethod::Type MetaMethod::type() const +{ + return d->type; +} + +MetaParameter::List MetaMethod::arguments() const +{ + return d->arguments; +} + +QUObject* MetaMethod::toQUObject(Variable::List arguments) +{ + uint argsize = d->arguments.size(); + + if(arguments.size() <= argsize) { + throw Exception(QString("To less arguments for slot with siganture \"%1\"").arg(d->signature)); + } + + // The first item in the QUObject-array is for the returnvalue + // while everything >=1 are the passed parameters. + QUObject* uo = new QUObject[ argsize + 1 ]; + + uo[0] = QUObject(); // empty placeholder for the returnvalue. + + for(uint i = 0; i < argsize; i++) { + KSharedPtr<MetaParameter> metaargument = d->arguments[i]; + KSharedPtr<Variable> variable = arguments[i + 1]; + + if ( !variable ) { + throw Exception(QString("Variable is undefined !")); + } + + if(metaargument->type() != variable->type()) { + throw Exception(QString("Wrong variable type in method \"%1\". Expected \"%2\" but got \"%3\"").arg(d->signature).arg(metaargument->type()).arg(variable->type())); + } + + switch(metaargument->type()) { + + case Variable::TypeNone: { + kdDebug() << "Variable::TypeNone" << endl; + uo[i + 1] = QUObject(); + } break; + + case Variable::TypeVariant: { + kdDebug() << "Variable::TypeVariant" << endl; + + const QVariant variant = variable->variant(); + switch(metaargument->variantType()) { + case QVariant::String: { + const QString s = variant.toString(); + static_QUType_QString.set( &(uo[i + 1]), s ); + } break; + case QVariant::Int: { + const int j = variant.toInt(); + static_QUType_int.set( &(uo[i + 1]), j ); + } break; + case QVariant::Bool: { + const bool b = variant.toBool(); + static_QUType_bool.set( &(uo[i + 1]), b ); + } break; + case QVariant::Double: { + const double d = variant.toDouble(); + static_QUType_double.set( &(uo[i + 1]), d ); + } break; + case QVariant::Invalid: { + static_QUType_QVariant.set( &(uo[i + 1]), variant ); + } + + /*FIXME + static_QUType_charstar + static_QUType_ptr.get(uo); QObject *qobj = (QObject *)(ptr); + */ + + default: { + throw Exception(QString("Invalid parameter !!!!!!!!!!!!!!!!!!!!!!!")); + } break; + } + } break; + + case Variable::TypeObject: { + kdDebug() << "Variable::TypeObject" << endl; + + const QObject* obj = arguments[i + 1]->object(); + if(! obj) { //FIXME: move check to MetaParameter?! + throw Exception(QString("No QObject !")); + } + static_QUType_ptr.set( &(uo[i + 1]), obj ); + } break; + + default: { + throw Exception(QString("Invalid variable type")); + } break; + } + + } + + return uo; +} + +KSharedPtr<Variable> MetaMethod::toVariable(QUObject* uo) +{ + const QString desc( uo->type->desc() ); + + if(desc == "null") { + return new Variable(); + } + + if(desc == "QString") { + const QString s = static_QUType_QString.get(uo); + return new Variable(s); + } + + if(desc == "int") { + const int j = static_QUType_int.get(uo); + return new Variable(j); + } + + if(desc == "bool") { + const bool b = static_QUType_bool.get(uo); + return new Variable(b); + } + + if(desc == "double") { + const double d = static_QUType_double.get(uo); + return new Variable(d); + } + + if(desc == "QVariant") { + QVariant v = static_QUType_QVariant.get(uo); + return new Variable(v); + } + + throw Exception(QString("Invalid parameter '%1'").arg(desc)); +} + +Variable::List MetaMethod::toVariableList(QUObject* uo) +{ + Variable::List list; + + MetaParameter::List::ConstIterator it, end( d->arguments.constEnd() ); + for( it = d->arguments.constBegin(); it != end; ++it) { + list.append( toVariable(uo) ); + uo++; + } + + return list; +} + +KSharedPtr<Variable> MetaMethod::invoke(Variable::List arguments) +{ + kdDebug() << "KSharedPtr<Variable> MetaMethod::invoke(Variable::List arguments)" << endl; + + if(! d->object) { + throw Exception("MetaObject is undefined."); + } + + QObject* obj = d->object->object(); + KSharedPtr<Variable> returnvalue; + QUObject* qu = 0; + + try { + qu = toQUObject(arguments); + + switch( d->type ) { + case Signal: { + int index = d->object->indexOfSignal( d->signature.latin1() ); + obj->qt_emit(index, qu); + } break; + case Slot: { + int index = d->object->indexOfSlot( d->signature.latin1() ); + obj->qt_invoke(index, qu); + } break; + default: { + throw Exception("Unknown type."); + } break; + } + returnvalue = toVariable( &qu[0] ); + } + catch(Exception& e) { + delete [] qu; // free the QUObject array and + kdDebug() << "EXCEPTION in KoMacro::MetaMethod::invoke(Variable::List)" << endl; + throw Exception(e); // re-throw exception + } + + delete [] qu; + return returnvalue; +} diff --git a/kexi/plugins/macros/lib/metamethod.h b/kexi/plugins/macros/lib/metamethod.h new file mode 100644 index 00000000..df53ac60 --- /dev/null +++ b/kexi/plugins/macros/lib/metamethod.h @@ -0,0 +1,150 @@ +/*************************************************************************** + * This file is part of the KDE project + * copyright (C) 2005 by Sebastian Sauer (mail@dipe.org) + * copyright (C) 2005 by Tobi Krebs (tobi.krebs@gmail.com) + * + * 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 KOMACRO_METAMETHOD_H +#define KOMACRO_METAMETHOD_H + +#include <qstring.h> +#include <qvaluelist.h> +#include <ksharedptr.h> + +#include "komacro_export.h" + +struct QUObject; + +namespace KoMacro { + + // forward declarations. + class Variable; + class MetaObject; + class MetaParameter; + class MetaProxy; + + /** + * Class to provide abstract methods for the undocumented + * Qt3 QUObject-API functionality. + * + * The design tried to limit future porting to Qt4 by providing a + * somewhat similar API to the Qt4 QMeta* stuff. + */ + class KOMACRO_EXPORT MetaMethod : public KShared + { + public: + + /** + * The type of method this @a MetaMethod provides + * access to. + */ + enum Type { + Signal, /// The @a MetaMethod points to a Qt signal. + Slot, /// The @a MetaMethod points to a Qt slot. + Unknown /// The @a MetaMethod is not known. + }; + + /** + * Constructor. + * + * @param signature The signature this @a MetaMethod has. This + * includes the tagname and the arguments and could look like + * "myslot(const QString&, int)". + * @param type The @a MetaMethod::Type the @a MethodMethod + * has. + * @param object The @a MetaObject this @a MethodMethod + * belongs to. Each @a MethodMethod is associated with + * exactly one @a MetaObject . + */ + explicit MetaMethod(const QString& signature, Type type = Unknown, KSharedPtr<MetaObject> object = 0); + + /** + * Destructor. + */ + ~MetaMethod(); + + /** + * @return the @a MetaObject instance this @a MethodMethod + * belongs to. + */ + KSharedPtr<MetaObject> const object() const; + + /** + * @return the signature this @a MetaMethod has. It could + * be something like "mySlot(const QString&,int)". + */ + const QString signature() const; + + /** + * @return the signatures tagname this @a MetaMethod has. + * At the signature "mySlot(const QString&,int)" the + * tagname would be "mySlot". + */ + const QString signatureTag() const; + + /** + * @return the signatures arguments this @a MetaMethod has. + * At the signature "mySlot(const QString&,int)" the + * arguments are "const QString&,int". + */ + const QString signatureArguments() const; + + /** + * @return the @a Type of method this @a MetaMethod provides + * access to. + */ + Type type() const; + + /** + * @return the signature arguments as parsed list of + * @a MetaParameter instances. + */ + QValueList< KSharedPtr<MetaParameter> > arguments() const; + + /** + * Translate the passed @p arguments list of @a Variable instances + * into a Qt3 QUObject* array. + */ + QUObject* toQUObject(QValueList< KSharedPtr<Variable> > arguments); + + /** + * Translate the passed @p uo QUObject reference into an internal used + * @a Variable instances. + */ + KSharedPtr<Variable> toVariable(QUObject* uo); + + /** + * Translate the passed @p uo QUObject array into an internal used + * list of @a Variable instances. + */ + QValueList< KSharedPtr<Variable> > toVariableList(QUObject* uo); + + /** + * Invoke the @a MetaMethod with the optional arguments + * @p arguments and return a variable. + */ + KSharedPtr<Variable> invoke(QValueList< KSharedPtr<Variable> > arguments); + + private: + /// @internal d-pointer class. + class Private; + /// @internal d-pointer instance. + Private* const d; + }; + +} + +#endif diff --git a/kexi/plugins/macros/lib/metaobject.cpp b/kexi/plugins/macros/lib/metaobject.cpp new file mode 100644 index 00000000..000f4181 --- /dev/null +++ b/kexi/plugins/macros/lib/metaobject.cpp @@ -0,0 +1,151 @@ +/*************************************************************************** + * This file is part of the KDE project + * copyright (C) 2005 by Sebastian Sauer (mail@dipe.org) + * copyright (C) 2005 by Tobi Krebs (tobi.krebs@gmail.com) + * + * 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 "metaobject.h" +#include "metamethod.h" +#include "variable.h" +#include "exception.h" + +#include <qguardedptr.h> +#include <qmetaobject.h> + +#include <kdebug.h> + +using namespace KoMacro; + +namespace KoMacro { + + /** + * @internal d-pointer class to be more flexible on future extension of the + * functionality without to much risk to break the binary compatibility. + */ + class MetaObject::Private + { + public: + + /** + * The QObject instance this @a MetaObject belongs to. + */ + QGuardedPtr<QObject> const object; + + /** + * Constructor. + */ + Private(QObject* const object) + : object(object) + { + } + }; + +} + +MetaObject::MetaObject(QObject* const object) + : KShared() + , d( new Private(object) ) // create the private d-pointer instance. +{ +} + +MetaObject::~MetaObject() +{ + delete d; +} + +QObject* const MetaObject::object() const +{ + if(! d->object) { + throw Exception(QString("Object is undefined.")); + } + return d->object; +} + +/* +QStrList MetaObject::signalNames() const +{ + return object()->metaObject()->signalNames(); +} + +QStrList MetaObject::slotNames() const +{ + return object()->metaObject()->slotNames(); +} +*/ + +int MetaObject::indexOfSignal(const char* signal) const +{ + QMetaObject* metaobject = object()->metaObject(); + int signalid = metaobject->findSignal(signal, false); + if(signalid < 0) { + throw Exception(QString("Invalid signal \"%1\"").arg(signal)); + } + return signalid; +} + +int MetaObject::indexOfSlot(const char* slot) const +{ + QMetaObject* metaobject = object()->metaObject(); + int slotid = metaobject->findSlot(slot, false); + if(slotid < 0) { + throw Exception(QString("Invalid slot \"%1\"").arg(slot)); + } + return slotid; +} + +KSharedPtr<MetaMethod> MetaObject::method(int index) +{ + QObject* obj = object(); + MetaMethod::Type type = MetaMethod::Slot; + QMetaObject* metaobject = obj->metaObject(); + + const QMetaData* metadata = metaobject->slot(index, true); + if(! metadata) { + // Try to get a signal with that index iff we failed to determinate + // a matching slot. + + metadata = metaobject->signal(index, true); + if(! metadata) { + throw Exception(QString("Invalid method index \"%1\" in object \"%2\"").arg(index).arg(obj->name())); + } + type = MetaMethod::Signal; + } + + if(metadata->access != QMetaData::Public) { + throw Exception(QString("Not allowed to access method \"%1\" in object \"%2\"").arg(metadata->name).arg(obj->name())); + } + + return new MetaMethod(metadata->name, type, this); +} + +KSharedPtr<MetaMethod> MetaObject::signal(const char* signal) +{ + return method( indexOfSignal(signal) ); +} + +KSharedPtr<MetaMethod> MetaObject::slot(const char* slot) +{ + return method( indexOfSlot(slot) ); +} + +KSharedPtr<Variable> MetaObject::invokeMethod(int index, Variable::List arguments) +{ + // kdDebug() << "MetaObject::invokeMethod(int index, Variable::List arguments)" << endl; + KSharedPtr<MetaMethod> m = method(index); + // kdDebug() << "MetaObject::invokeMethod(int index, Variable::List arguments) return" << endl; + return m->invoke(arguments); +} + diff --git a/kexi/plugins/macros/lib/metaobject.h b/kexi/plugins/macros/lib/metaobject.h new file mode 100644 index 00000000..8b611574 --- /dev/null +++ b/kexi/plugins/macros/lib/metaobject.h @@ -0,0 +1,118 @@ +/*************************************************************************** + * This file is part of the KDE project + * copyright (C) 2005 by Sebastian Sauer (mail@dipe.org) + * copyright (C) 2005 by Tobi Krebs (tobi.krebs@gmail.com) + * + * 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 KOMACRO_METAOBJECT_H +#define KOMACRO_METAOBJECT_H + +#include <qobject.h> +#include <ksharedptr.h> + +#include "komacro_export.h" + +namespace KoMacro { + + // forward declarations. + class Variable; + class MetaMethod; + + /** + * Class to provide abstract access to extended QObject functionality + * like the undocumented QUObject-API in Qt3. + * + * The design tried to limit future porting to Qt4 by providing a + * somewhat similar API to the Qt4 QMeta* stuff. + */ + class KOMACRO_EXPORT MetaObject : public KShared + { + public: + + /** + * Constructor. + * + * @param object The QObject instance this @a MetaObject provides + * abstract access to. + */ + explicit MetaObject(QObject* const object); + + /** + * Destructor. + */ + ~MetaObject(); + + /** + * @return the QObject this @a MetaObject provides abstract + * access to. + */ + QObject* const object() const; + + //QStrList signalNames() const; + //QStrList slotNames() const; + + /** + * @return the index of the signal @p signal . + */ + int indexOfSignal(const char* signal) const; + + /** + * @return the index of the slot @p slot . + */ + int indexOfSlot(const char* slot) const; + + /** + * @return the @a MetaMethod that matches to the + * index @p index . + */ + KSharedPtr<MetaMethod> method(int index); + + /** + * @return a @a MetaMethod for the signal @p signal . + */ + KSharedPtr<MetaMethod> signal(const char* signal); + + /** + * @return a @a MetaMethod for the slot @p slot . + */ + KSharedPtr<MetaMethod> slot(const char* slot); + +//KSharedPtr<MetaMethod> addSlot(const char* slot); +//void connectSignal(QObject* obj, const char* signal); + + /** + * Invoke the @a MetaMethod that has the index @p index . + * + * @param index The index the signal or slot has. Use + * @a indexOfSignal() and @a indexOfSlot() to determinate + * those index. + * @param arguments The optional arguments passed to the + * method. + * @return The returnvalue the method provides and that got + * returned if the execution is done. + */ + KSharedPtr<Variable> invokeMethod(int index, QValueList< KSharedPtr<Variable> > arguments); + + private: + /// @internal d-pointer class. + class Private; + /// @internal d-pointer instance. + Private* const d; + }; + +} + +#endif diff --git a/kexi/plugins/macros/lib/metaparameter.cpp b/kexi/plugins/macros/lib/metaparameter.cpp new file mode 100644 index 00000000..7f072b2b --- /dev/null +++ b/kexi/plugins/macros/lib/metaparameter.cpp @@ -0,0 +1,146 @@ +/*************************************************************************** + * This file is part of the KDE project + * copyright (C) 2005 by Sebastian Sauer (mail@dipe.org) + * copyright (C) 2005 by Tobi Krebs (tobi.krebs@gmail.com) + * + * 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 "metaparameter.h" +#include "exception.h" +#include "variable.h" + +#include <kdebug.h> + +using namespace KoMacro; + +namespace KoMacro { + + /** + * @internal d-pointer class to be more flexible on future extension of the + * functionality without to much risk to break the binary compatibility. + */ + class MetaParameter::Private + { + public: + + /** + * The signatures argument that represents this MetaParameter. + * This could be something like "const QString&", "int" or + * "QMap < QString, QVariant > ". + */ + QString signatureargument; + + /** + * The type of the @a Variable . + */ + MetaParameter::Type type; + + /** + * If the @a MetaParameter::Type is a Variant this QVariant::Type + * is used to defined what kind of Variant it is. + */ + QVariant::Type varianttype; + + }; + +} + +MetaParameter::MetaParameter(const QString& signatureargument) + : KShared() + , d( new Private() ) // create the private d-pointer instance. +{ + d->type = TypeNone; + + if(! signatureargument.isNull()) { + setSignatureArgument( signatureargument ); + } +} + +MetaParameter::~MetaParameter() +{ + delete d; +} + +MetaParameter::Type MetaParameter::type() const +{ + return d->type; +} + +const QString MetaParameter::typeName() const +{ + switch( d->type ) { + case TypeNone: + return "None"; + case TypeVariant: + return "Variant"; + case TypeObject: + return "Object"; + } + return QString::null; +} + +void MetaParameter::setType(MetaParameter::Type type) +{ + d->type = type; + d->varianttype = QVariant::Invalid; +} + +QVariant::Type MetaParameter::variantType() const +{ + return d->varianttype; +} + +void MetaParameter::setVariantType(QVariant::Type varianttype) +{ + d->type = TypeVariant; + d->varianttype = varianttype; +} + +void MetaParameter::setSignatureArgument(const QString& signatureargument) +{ + d->signatureargument = signatureargument; + + QString argument = signatureargument; + if(argument.startsWith("const")) { + argument = argument.mid(5).stripWhiteSpace(); + } + + if(argument.endsWith("&")) { + argument = argument.left( argument.length() - 1 ).stripWhiteSpace(); + } + + if(argument.isEmpty()) { + throw Exception(QString("Empty signature argument passed.")); + } + if(argument == "QVariant") { + setVariantType( QVariant::Invalid ); + } + + QVariant::Type type = argument.isNull() ? QVariant::Invalid : QVariant::nameToType(argument.latin1()); + if (type != QVariant::Invalid) { + setVariantType( type ); + } + else { + setType( TypeObject ); + } +} + +bool MetaParameter::validVariable(KSharedPtr<Variable> variable) const +{ + if( type() != variable->type() ) { + return false; + } + return true; +} diff --git a/kexi/plugins/macros/lib/metaparameter.h b/kexi/plugins/macros/lib/metaparameter.h new file mode 100644 index 00000000..ab2a4004 --- /dev/null +++ b/kexi/plugins/macros/lib/metaparameter.h @@ -0,0 +1,136 @@ +/*************************************************************************** + * This file is part of the KDE project + * copyright (C) 2005 by Sebastian Sauer (mail@dipe.org) + * copyright (C) 2005 by Tobi Krebs (tobi.krebs@gmail.com) + * + * 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 KOMACRO_METAPARAMETER_H +#define KOMACRO_METAPARAMETER_H + +#include <qstring.h> +#include <qvariant.h> +#include <qobject.h> +#include <ksharedptr.h> + +#include "komacro_export.h" + +namespace KoMacro { + + // Forward declarations. + class Variable; + + /** + * Class to provide abstract methods for the undocumented + * Qt3 QUObject-API functionality. + * + * The design tried to limit future porting to Qt4 by providing a + * somewhat similar API to the Qt4 QMeta* stuff. + */ + class KOMACRO_EXPORT MetaParameter : public KShared + { + + /** + * Property to get the type of the variable. + */ + Q_PROPERTY(Type type READ type) + + /** + * Property to get the type of the variable as string. + */ + Q_PROPERTY(QString typeName READ typeName) + + public: + + /** + * List of @a MetaParameter instances. + */ + typedef QValueList<KSharedPtr <MetaParameter > > List; + + /** + * Constructor. + * + * @param signatureargument The signatures argument + * that will be used to determinate the arguments + * type. This could be something like "const QString&", + * "int" or "QMap < QString, QVariant > ". + */ + explicit MetaParameter(const QString& signatureargument = QString::null); + + /** + * Destructor. + */ + ~MetaParameter(); + + /** + * Possible types the @a MetaParameter could provide. + */ + enum Type { + TypeNone = 0, /// None type, the @a MetaParameter is empty. + TypeVariant, /// The @a MetaParameter is a QVariant. + TypeObject /// The @a MetaParameter is a QObject. + }; + + /** + * @return the @a MetaParameter::Type this variable has. + */ + Type type() const; + + /** + * @return the @a MetaParameter::Type as string. The typename + * could be "None", "Variant" or "Object". + */ + const QString typeName() const; + + /** + * Set the @a MetaParameter::Type this variable is. + */ + void setType(Type type); + + /** + * @return the @a MetaParameter::Type this variable is. + */ + QVariant::Type variantType() const; + + /** + * Set the @a MetaParameter::Type this variable is. + */ + void setVariantType(QVariant::Type varianttype); + + /** + * @return true if the passed @a Variable @p variable is + * valid for this @a MetaParameter . Valid means, that + * the variable has a castable type. + */ + bool validVariable(KSharedPtr<Variable> variable) const; + + protected: + + /** + * @internal used method to set the signature argument. Those + * argument will be used to determinate the arguments type. + */ + void setSignatureArgument(const QString& signatureargument); + + private: + /// @internal d-pointer class. + class Private; + /// @internal d-pointer instance. + Private* const d; + }; + +} + +#endif diff --git a/kexi/plugins/macros/lib/variable.cpp b/kexi/plugins/macros/lib/variable.cpp new file mode 100644 index 00000000..598b8b46 --- /dev/null +++ b/kexi/plugins/macros/lib/variable.cpp @@ -0,0 +1,246 @@ +/*************************************************************************** + * This file is part of the KDE project + * copyright (C) 2005 by Sebastian Sauer (mail@dipe.org) + * copyright (C) 2005 by Tobi Krebs (tobi.krebs@gmail.com) + * + * 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 "variable.h" +#include "exception.h" + +#include <kdebug.h> + +using namespace KoMacro; + +namespace KoMacro { + + /** + * @internal d-pointer class to be more flexible on future extension of the + * functionality without to much risk to break the binary compatibility. + */ + class Variable::Private + { + public: + + /** + * The name this @a Variable has. + */ + QString name; + + /** + * The i18n-caption used for display purposes only + * this @a Variable has. + */ + QString text; + + /** + * If @a Variable::Type is @a Variable::TypeVariant this QVariant + * holds the value else it's invalid. + */ + QVariant variant; + + /** + * If @a Variable::Type is @a Variable::TypeObject this QObject is + * the value else it's NULL. + */ + const QObject* object; + + /** + * Optional list of children this @a Variable has. + */ + // TODO Dow we use this or is it for the future?? + Variable::List children; + + /** + * Defines if the variable is enabled or disabled. + */ + bool enabled; + + explicit Private() + : enabled(true) + { + } + }; + +} + +Variable::Variable() + : MetaParameter() + , d( new Private() ) // create the private d-pointer instance. +{ + setType(TypeNone); + d->object = 0; +} + +Variable::Variable(const QVariant& variant, const QString& name, const QString& text) + : MetaParameter() + , d( new Private() ) // create the private d-pointer instance. +{ + setVariantType(variant.type()); + d->variant = variant; + d->object = 0; + d->name = name; + d->text = text; +} + +Variable::Variable(const QObject* object) + : MetaParameter() + , d( new Private() ) // create the private d-pointer instance. +{ + setType(TypeObject); + d->object = object; +} + +Variable::Variable(const QDomElement& element) + : MetaParameter() + , d( new Private() ) // create the private d-pointer instance. +{ + + QString typesignature = element.attribute("type", "const QString&"); + QString value = element.text(); + + setSignatureArgument( typesignature ); + + switch( type() ) { + case KoMacro::MetaParameter::TypeVariant: { + //kdDebug() << QString("KoMacro::Variable(QDomElement) KoMacro::MetaParameter::TypeVariant") << endl; + // Set the variant without overwritting the previously detected varianttype. + setVariant( QVariant(value), false ); + } break; + case KoMacro::MetaParameter::TypeObject: { + //kdDebug() << QString("KoMacro::Variable(QDomElement) KoMacro::MetaParameter::TypeObject") << endl; + //TODO setObject(); + } break; + default: { + kdWarning() << QString("KoMacro::Variable(QDomElement) KoMacro::MetaParameter::TypeNone") << endl; + } break; + } +} + +Variable::~Variable() +{ + delete d; +} + +QString Variable::name() const +{ + return d->name; +} + +void Variable::setName(const QString& name) +{ + d->name = name; +} + +QString Variable::text() const +{ + return d->text; +} + +void Variable::setText(const QString& text) +{ + d->text = text; +} + +const QVariant Variable::variant() const +{ + //Q_ASSERT( type() == MetaParameter::TypeVariant ); + //Q_ASSERT( variantType() != QVariant::Invalid ); + //if(variantType() == QVariant::Invalid) return QVariant(); + return d->variant; +} + +void Variable::setVariant(const QVariant& variant, bool detecttype) +{ + if(detecttype) { + setVariantType( variant.type() ); + } + d->variant = variant; +} + +const QObject* Variable::object() const +{ + Q_ASSERT( ! d->object ); + return d->object; +} + +void Variable::setObject(const QObject* object) +{ + setType(TypeObject); + d->object = object; +} + +Variable::operator QVariant () const +{ + return variant(); +} + +Variable::operator const QObject* () const +{ + return object(); +} + +const QString Variable::toString() const +{ + switch( type() ) { + case KoMacro::MetaParameter::TypeVariant: { + return variant().toString(); + } break; + case KoMacro::MetaParameter::TypeObject: { + return QString("[%1]").arg( object()->name() ); + } break; + default: { + throw Exception("Type is undefined."); + } break; + } + return QString::null; +} + +int Variable::toInt() const +{ + return variant().toInt(); +} + +Variable::List Variable::children() const +{ + return d->children; +} + +void Variable::appendChild(KSharedPtr<Variable> variable) +{ + d->children.append(variable); +} + +void Variable::clearChildren() +{ + d->children.clear(); +} + +void Variable::setChildren(const Variable::List& children) +{ + d->children = children; +} + +/* +bool Variable::isEnabled() const +{ + return d->enabled; +} + +void Variable::setEnabled(const bool enabled) +{ + d->enabled = enabled; +} +*/ diff --git a/kexi/plugins/macros/lib/variable.h b/kexi/plugins/macros/lib/variable.h new file mode 100644 index 00000000..26e9619e --- /dev/null +++ b/kexi/plugins/macros/lib/variable.h @@ -0,0 +1,222 @@ +/*************************************************************************** + * This file is part of the KDE project + * copyright (C) 2005 by Sebastian Sauer (mail@dipe.org) + * copyright (C) 2005 by Tobi Krebs (tobi.krebs@gmail.com) + * + * 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 KOMACRO_VARIABLE_H +#define KOMACRO_VARIABLE_H + +#include <qobject.h> +#include <qdom.h> +#include <qvariant.h> +#include <ksharedptr.h> + +#include "metaparameter.h" + +namespace KoMacro { + + /** + * A variable value used to provide abstract access to variables. The + * class handles QVariant and QObject and provides access to them. + * Variable inherits KShared and implements reference couting. So, it's + * not needed to take care of memory-managment. + */ + class KOMACRO_EXPORT Variable : public MetaParameter + { + + /** + * Property to get and set a QVariant as variable. + */ + Q_PROPERTY(QVariant variant READ variant WRITE setVariant) + + /** + * Property to get and set a QObject as variable. + */ + Q_PROPERTY(QObject* object READ object WRITE setObject) + + /** + * Property to get a string-representation of the variable. + */ + Q_PROPERTY(QString string READ toString) + + public: + + /** + * A list of variables. + */ + typedef QValueList<KSharedPtr<Variable > > List; + + /** + * A map of variables. + */ + typedef QMap<QString, KSharedPtr<Variable > > Map; + + /** + * Default constructor. + */ + explicit Variable(); + + /** + * Constructor from the QVariant @p variant . + * + * @param variant The value this variable has. + * @param name The unique @a name() this variable has. + * @param text The describing @a text() this variable has. + */ + Variable(const QVariant& variant, const QString& name = QString::null, const QString& text = QString::null); + + /** + * Constructor from the QObject @p object . + * + * @param object The value this variable has. + */ + Variable(const QObject* object); + + /** + * Constructor from the QDomElement @p element . + * @deprecated replaced with methods of @a XMLHandler. + * @param element The QDomElement that may optional contains the + * variable content or other additional informations. + */ + Variable(const QDomElement& element); + + /** + * Destructor. + */ + virtual ~Variable(); + + /** + * @return the name this @a Variable has. + */ + QString name() const; + + /** + * Set the name @param name this @a Variable has. + */ + void setName(const QString& name); + + /** + * @return the caption this @a Variable has. + */ + QString text() const; + + /** + * Set the caption @param text this @a Variable has. + */ + void setText(const QString& text); + + /** + * Set the QObject @param object this variable has. A + * previously remembered value will be overwritten and + * the new type is a @a TypeObject . + */ + void setObject(const QObject* object); + + /** + * @return the QVariant this variable has. If this + * variable isn't a @a TypeVariant an invalid QVariant + * got returned. + */ + const QVariant variant() const; + + /** + * Set the QVariant @param variant this variable has. A + * previously remembered value will be overwritten and + * the new type is a @a TypeVariant . If @param detecttype is + * true the method tries to set the @a variantType according + * to the passed QVariant. If false the variantType won't + * be changed. + */ + void setVariant(const QVariant& variant, bool detecttype = true); + + /** + * @return the QObject this variable has. If this + * variable isn't a @a TypeObject NULL got returned. + */ + const QObject* object() const; + + /** + * Implicit conversion to QVariant operator. This method + * calls @a variant() internaly. + */ + operator QVariant () const; + + /** + * Implicit conversion to QObject operator. This method + * calls @a object() internaly. + */ + operator const QObject* () const; + + /** + * @return a string-represenation of the variable. + */ + const QString toString() const; + + /** + * @return a integer-represenation of the variable. + */ + int toInt() const; + + /** + * @return the optional list of @a Variable instances + * that are children of this @a Variable . + * + * @note that the list is returned call-by-reference. The + * list is accessed as getter/setter (read/write). So, + * don't set this method to const! + */ + List children() const; + + /** + * Append a @a Variable to the list of children this + * @a Variable has. + */ + void appendChild(KSharedPtr<Variable> variable); + + /** + * Clear the list of children this @a Variable has. + */ + void clearChildren(); + + /** + * Set the children this @a Variable has. + */ + void setChildren(const List& children); + +#if 0 + /** + * @return true if this @a Variable is enabled else + * false is returned. + */ + bool isEnabled() const; + + /** + * Set this @a Variable to be enabled if @param enabled is + * true else the variable is disabled. + */ + void setEnabled(const bool enabled); +#endif + private: + /// @internal d-pointer class. + class Private; + /// @internal d-pointer instance. + Private* const d; + }; + +} + +#endif diff --git a/kexi/plugins/macros/lib/xmlhandler.cpp b/kexi/plugins/macros/lib/xmlhandler.cpp new file mode 100644 index 00000000..b35759e1 --- /dev/null +++ b/kexi/plugins/macros/lib/xmlhandler.cpp @@ -0,0 +1,226 @@ +/*************************************************************************** + * This file is part of the KDE project + * copyright (C) 2005 by Sebastian Sauer (mail@dipe.org) + * copyright (C) 2006 by Bernd Steindorff (bernd@itii.de) + * + * 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 "xmlhandler.h" +#include "macro.h" +#include "macroitem.h" +#include "action.h" + +#include <qdom.h> +#include <kdebug.h> + +using namespace KoMacro; + +namespace KoMacro { + + /** + * @internal d-pointer class to be more flexible on future extension of the + * functionality without to much risk to break the binary compatibility. + */ + class XMLHandler::Private + { + public: + + /** + * The @a Macro instance this @a XMLHandler + * manages. + */ + Macro* const macro; + + /** + * Constructor. + * + * @param macro The @a Macro instance this + * @a XMLHandler manages. + */ + Private(Macro* const macro) + : macro(macro) + { + } + }; + +} + +XMLHandler::XMLHandler(Macro* const macro) + : d( new Private(macro) ) +{ +} + +XMLHandler::~XMLHandler() +{ + delete d; +} + +bool XMLHandler::parseXML(const QDomElement& element) +{ + // Remove old items. We should clear first. + d->macro->clearItems(); + + // We expect a <macro> element. Do we really need to be such strict or + // would it be more wise to trust the application in that case? + if(element.tagName() != "macro") { + kdDebug() << QString("XMLHandler::parseXML() Invalid tagname \"%1\"").arg(element.tagName()) << endl; + return false; + } + + // To be flexible with the xml-scheme, we need a version-number for xml. + // If there is more than one version, parsing should update old macro-data, so that it + // could write out in the newer version in toXML(). + if( element.attribute("xmlversion") != "1"){ + kdDebug() << QString("XMLHandler::parseXML() Invalid xml-version \"%1\"").arg(element.attribute("xmlversion")) << endl; + return false; + } + + // Do we need to load the macro's name? + // d->macro->setName(element.attribute("name")); + + // Iterate through the child nodes the passed QDomElement has and + // build the MacroItem elements. + for(QDomNode itemnode = element.firstChild(); ! itemnode.isNull(); itemnode = itemnode.nextSibling()) { + // The tagname should be "item" + if(itemnode.nodeName() == "item") { + // The node is an element. + const QDomElement itemelem = itemnode.toElement(); + + // Create a new MacroItem + KSharedPtr<MacroItem> item = new MacroItem(); + + // Add the new item to our Macro. + d->macro->addItem( item ); + + // Each MacroItem may point to an Action instance. We + // try to determinate this action now and if it's defined + // and available, we set it. + KSharedPtr<Action> action = Manager::self()->action( itemelem.attribute("action") ); + if(action.data()) { + item->setAction(action); + } + + // Set the comment + item->setComment( itemelem.attribute("comment") ); + + // Iterate through the children this item has and try + // to fill the list of variables our new MacroItem has. + for(QDomNode childnode = itemnode.firstChild(); ! childnode.isNull(); childnode = childnode.nextSibling()) { + // The tagname should be "variable" + if(childnode.nodeName() == "variable") { + // The node is an element. + const QDomElement childelem = childnode.toElement(); + + // The name the variable has. + const QString name = childelem.attribute("name"); + // The value the variable has. + const QString value = childelem.text(); + + // Store the new variable in our macroitem. + item->addVariable(name, value); + } + } + } + } + + // Job was done successfully. + return true; +} + +QDomElement XMLHandler::toXML() +{ + // The QDomDocument provides us the functionality to create new QDomElement instances. + QDomDocument document; + + // Create the Macro-QDomElement. This element will be returned. + QDomElement macroelem = document.createElement("macro"); + + // Set the Macro-XML-Version, it should be the newest Version. + macroelem.setAttribute("xmlversion","1"); + + // Do we need to store the macro's name? Normaly the application + // could/should take care of it cause we don't know how the app + // may store the XML and cause we don't like to introduce + // redundancy at this point. + //macroelem.setAttribute("name",d->macro->name()); + + // The list of MacroItem-children a Macro provides. + QValueList<KSharedPtr<MacroItem > > items = d->macro->items(); + + // Create an iterator... + QValueList<KSharedPtr<MacroItem > >::ConstIterator it(items.constBegin()), end(items.constEnd()); + // ...and iterate over the list of children the Macro provides. + for(;it != end; ++it) { + // We are iterating over MacroItem instances. + KSharedPtr<MacroItem> item = *it; + + // Flag to determinate if we really need to remember this item what + // is only the case if comment or action is defined. + bool append = false; + + // Each MacroItem will have an own node. + QDomElement itemelem = document.createElement("item"); + + // Each MacroItem could point to an Action provided by the Manager. + const KSharedPtr<Action> action = item->action(); + if( action ) { + append = true; + + // Remember the name of the action. + itemelem.setAttribute("action", action->name()); + + // Each MacroItem could have a list of variables. We + // iterate through that list and build a element + // for each single variable. + QMap<QString, KSharedPtr<Variable > > varmap = item->variables(); + + for(QMap<QString, KSharedPtr<Variable > >::ConstIterator vit = varmap.constBegin(); vit != varmap.constEnd(); ++vit) { + const KSharedPtr<Variable> v = vit.data(); + if(! v.data()) { + // skip if the variable is NULL. + continue; + } + // Create an own element for the variable. The tagname will be + // the name of the variable. + QDomElement varelement = document.createElement("variable"); + + // Remember the name the value has. + varelement.setAttribute("name", vit.key()); + + // Remember the value as textnode. + varelement.appendChild(document.createTextNode(v->toString())); + + // Add the new variable-element to our MacroItem. + itemelem.appendChild(varelement); + } + } + + // Each MacroItem could have an optional comment. + const QString comment = item->comment(); + if(! comment.isEmpty()) { + append = true; + itemelem.setAttribute("comment", item->comment()); + } + + // Check if we really need to remember the item. + if(append) { + macroelem.appendChild(itemelem); + } + + } + + // Job done. Return the macro's element. + return macroelem; +} diff --git a/kexi/plugins/macros/lib/xmlhandler.h b/kexi/plugins/macros/lib/xmlhandler.h new file mode 100644 index 00000000..b6978d0f --- /dev/null +++ b/kexi/plugins/macros/lib/xmlhandler.h @@ -0,0 +1,77 @@ +/*************************************************************************** + * This file is part of the KDE project + * copyright (C) 2005 by Sebastian Sauer (mail@dipe.org) + * copyright (C) 2006 by Bernd Steindorff (bernd@itii.de) + * + * 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 KOMACRO_XMLHANDLER_H +#define KOMACRO_XMLHANDLER_H + +#include "komacro_export.h" + +class QObject; +class QDomElement; + +namespace KoMacro { + + // Forward declarations. + class Macro; + + /** + * The XMLHandler class manages the (un-)serialization of + * a @a Macro instance to/from XML. + */ + class KOMACRO_EXPORT XMLHandler + { + public: + + /** + * Constructor to init a @a XMLHandler . + * @param macro The @a Macro instance which will + * be managed. + */ + XMLHandler(Macro* const macro); + + /** + * Destructor to @a XMLHandler . + */ + ~XMLHandler(); + + /** + * Reads a given @a QDomElement, extracts given + * Actions into the managed Macro-Instance. + * @param element The @a QDomElement within + * the @a Macro. + * @return Return true when parsing is successfull. + */ + bool parseXML(const QDomElement& element); + + /** + * Converts the macro to a @a QDomElement. + * @return The resulten @a QDomElement from + * the @a Macro. + */ + QDomElement toXML(); + + private: + /// @internal d-pointer class. + class Private; + /// @internal d-pointer instance. + Private* const d; + }; +} + +#endif diff --git a/kexi/plugins/macros/tests/Makefile.am b/kexi/plugins/macros/tests/Makefile.am new file mode 100644 index 00000000..36dbd76f --- /dev/null +++ b/kexi/plugins/macros/tests/Makefile.am @@ -0,0 +1,28 @@ +if include_kunittestgui + GUIBINPROGRAM = komacrotestgui +else + GUIBINPROGRAM = +endif + +bin_PROGRAMS = komacrotest $(GUIBINPROGRAM) + +komacrotest_SOURCES = komacrotest.cpp testobject.cpp testaction.cpp actiontests.cpp macrotests.cpp macroitemtests.cpp variabletests.cpp xmlhandlertests.cpp xmlhandlertests2.cpp +komacrotest_LDFLAGS = $(KDE_RPATH) $(all_libraries) +komacrotest_LDADD = -lkunittest ../lib/libkomacro.la $(LIB_KDEUI) $(LIB_KPARTS) + +if include_kunittestgui + komacrotestgui_SOURCES = komacrotestgui.cpp testobject.cpp testaction.cpp actiontests.cpp macrotests.cpp macroitemtests.cpp variabletests.cpp xmlhandlertests.cpp xmlhandlertests2.cpp + komacrotestgui_LDFLAGS = $(KDE_RPATH) $(all_libraries) + komacrotestgui_LDADD = -lkunittestgui ../lib/libkomacro.la $(LIB_KDEUI) $(LIB_KPARTS) +endif + +KDE_CXXFLAGS = $(USE_EXCEPTIONS) +INCLUDES = -I$(srcdir)/tests -I$(srcdir)../ $(all_includes) +METASOURCES = AUTO + +guicheck: komacrotestgui + kunittest ./komacrotestgui + +check: komacrotest + echo $(srcdir) + kunittest ./komacrotest diff --git a/kexi/plugins/macros/tests/actiontests.cpp b/kexi/plugins/macros/tests/actiontests.cpp new file mode 100644 index 00000000..0150ecfd --- /dev/null +++ b/kexi/plugins/macros/tests/actiontests.cpp @@ -0,0 +1,211 @@ +/*************************************************************************** + * This file is part of the KDE project + * copyright (C) 2006 by Sebastian Sauer (mail@dipe.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 "actiontests.h" +#include "testobject.h" +#include "testaction.h" +#include "komacrotestbase.h" + +#include "../lib/action.h" +#include "../lib/function.h" +#include "../lib/manager.h" +#include "../lib/macro.h" +#include "../lib/variable.h" +#include "../lib/metaobject.h" +#include "../lib/metamethod.h" +#include "../lib/metaparameter.h" +#include "../lib/exception.h" +#include "../lib/macroitem.h" + +#include <ostream> + +#include <qstringlist.h> +#include <qdom.h> + +#include <kdebug.h> +#include <kunittest/runner.h> +#include <kxmlguiclient.h> + +using namespace KUnitTest; +using namespace KoMacroTest; + +namespace KoMacroTest { + + /** + * Register KoMacroTest::CommonTests as TestSuite. + */ + + KUNITTEST_SUITE("KoMacroTestSuite"); + KUNITTEST_REGISTER_TESTER(ActionTests); + + + class ActionTests::Private + { + public: + /** + * An KXMLGUIClient instance created on @a setUp() and + * passed to the @a KoMacro::Manager to bridge to the + * app-functionality. + */ + KXMLGUIClient* xmlguiclient; + + /** + * An @a TestObject instance used internaly to test + * handling and communication with from QObject + * inheritated instances. + */ + TestAction* testaction; + + QDomDocument* doomdocument; + + KSharedPtr<KoMacro::Macro> macro; + + QValueList< KSharedPtr<KoMacro::MacroItem> > items; + + KSharedPtr<KoMacro::Action> actionptr; + + Private() + : xmlguiclient(0) + , testaction(0) + , doomdocument(0) + , macro(0) + , actionptr(0) + { + } + }; +} + +typedef QValueList< KSharedPtr<KoMacro::MacroItem> >::size_type sizetype; + + +ActionTests::ActionTests() + : KUnitTest::SlotTester() + , d( new Private() ) // create the private d-pointer instance. +{ +} + +ActionTests::~ActionTests() +{ + delete d->xmlguiclient; + delete d; +} + + +void ActionTests::setUp() +{ + d->xmlguiclient = new KXMLGUIClient(); + + if (::KoMacro::Manager::self() == 0) { + ::KoMacro::Manager::init( d->xmlguiclient ); + } + + d->testaction = new TestAction(); + ::KoMacro::Manager::self()->publishAction(d->testaction); + + d->doomdocument = new QDomDocument(); + + QString const xml = QString("<!DOCTYPE macros>" + "<macro xmlversion=\"1\" >" + "<item action=\"testaction\" >" + "</item>" + "</macro>"); + + d->doomdocument->setContent(xml); + d->macro = KoMacro::Manager::self()->createMacro("testMacro"); + d->macro->parseXML(d->doomdocument->documentElement()); + d->macro->execute(this); + d->items = d->macro->items(); + d->actionptr = d->items[0]->action(); +} + +void ActionTests::tearDown() +{ + delete d->actionptr; + delete d->macro; + delete d->doomdocument; + delete d->xmlguiclient; +} + + +void ActionTests::testMacro() +{ + kdDebug()<<"===================== testMacro() ======================" << endl; + + //fetch Items and .. + //QValueList< KSharedPtr<KoMacro::MacroItem> >& items = d->macro->items(); + + //... check that there is one + KOMACROTEST_XASSERT( d->items.count(), sizetype(0) ); +} + +void ActionTests::testAction() +{ + kdDebug()<<"===================== testAction() ======================" << endl; + + //get it + //KSharedPtr<KoMacro::Action> actionptr = d->items[0]->action(); + //-> check that it is not null + KOMACROTEST_XASSERT(sizetype(d->actionptr.data()), sizetype(0)); +} + +void ActionTests::testText() +{ + kdDebug()<<"===================== testText() ======================" << endl; + + //KSharedPtr<KoMacro::Action> actionptr = items[0]->action(); + + const QString leetSpeech = "']['3 $']['"; + + //check i18n text + KOMACROTEST_ASSERT(d->actionptr->text(),QString("Test")); + //change it + d->actionptr->setText(leetSpeech); + //retest it + KOMACROTEST_ASSERT(d->actionptr->text(),QString(leetSpeech)); +} + + +void ActionTests::testName() +{ + kdDebug()<<"===================== testName() ======================" << endl; + + //KSharedPtr<KoMacro::Action> actionptr = items[0]->action(); + + //check name + KOMACROTEST_ASSERT(d->actionptr->name(),QString("testaction")); + //change it + d->actionptr->setName("ActionJackson"); + //retest it + KOMACROTEST_ASSERT(d->actionptr->name(),QString("ActionJackson")); +} + +void ActionTests::testComment() +{ + kdDebug()<<"===================== testComment() ======================" << endl; + + //KSharedPtr<KoMacro::Action> actionptr = items[0]->action(); + + //check comment + KOMACROTEST_XASSERT(d->actionptr->comment(),QString("No Comment!")); + //set comment + d->actionptr->setComment("Stringtest"); + //check comment again + KOMACROTEST_ASSERT(d->actionptr->comment(),QString("Stringtest")); +} + +#include "actiontests.moc" diff --git a/kexi/plugins/macros/tests/actiontests.h b/kexi/plugins/macros/tests/actiontests.h new file mode 100644 index 00000000..48b5a252 --- /dev/null +++ b/kexi/plugins/macros/tests/actiontests.h @@ -0,0 +1,89 @@ +/*************************************************************************** + * This file is part of the KDE project + * copyright (C) 2005 by Sebastian Sauer (mail@dipe.org) + * copyright (C) 2005 by Tobi Krebs (tobi.krebs@gmail.com) + * + * 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 KOMACROTEST_ACTIONTESTS_H +#define KOMACROTEST_ACTIONTESTS_H + +#include <kunittest/tester.h> + +namespace KoMacroTest { + + /** + * The common testsuite used to test common @a KoMacro + * functionality. + */ + class ActionTests : public KUnitTest::SlotTester + { + Q_OBJECT + public: + + /** + * Constructor. + */ + ActionTests(); + + /** + * Destructor. + */ + virtual ~ActionTests(); + + public slots: + + /** + * This slot got called by KUnitTest before testing + * starts. + */ + void setUp(); + + /** + * This slot got called by KUnitTest after all tests + * are done. + */ + void tearDown(); + + /** + * Subtest for the @a KoMacro::Action functionality. + */ + void testMacro(); + + /** + * Test the @a KoMacro::Action functionality. + */ + void testAction(); + + void testText(); + /** + * Subtest for the @a KoMacro::Action functionality. + */ + void testName(); + /** + * Subtest for the @a KoMacro::Action functionality. + */ + void testComment(); + + private: + /// @internal d-pointer class. + class Private; + /// @internal d-pointer instance. + Private* const d; + }; + +} + +#endif diff --git a/kexi/plugins/macros/tests/commontests.cpp b/kexi/plugins/macros/tests/commontests.cpp new file mode 100644 index 00000000..84c596aa --- /dev/null +++ b/kexi/plugins/macros/tests/commontests.cpp @@ -0,0 +1,907 @@ +/*************************************************************************** + * This file is part of the KDE project + * copyright (C) 2005 by Sebastian Sauer (mail@dipe.org) + * copyright (C) 2005 by Tobi Krebs (tobi.krebs@gmail.com) + * + * 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 "commontests.h" +#include "testobject.h" +#include "testaction.h" +#include "komacrotestbase.h" + +#include "../lib/action.h" +#include "../lib/function.h" +#include "../lib/manager.h" +#include "../lib/macro.h" +#include "../lib/variable.h" +#include "../lib/metaobject.h" +#include "../lib/metamethod.h" +#include "../lib/metaparameter.h" +#include "../lib/exception.h" +#include "../lib/macroitem.h" + +#include <ostream> +#include <climits> + +#include <qstringlist.h> +#include <qdom.h> + +#include <kdebug.h> +#include <kunittest/runner.h> +#include <kxmlguiclient.h> + +using namespace KUnitTest; +using namespace KoMacroTest; + +namespace KoMacroTest { + + /** + * Register KoMacroTest::CommonTests as TestSuite. + */ + KUNITTEST_SUITE("CommonTestsSuite") + KUNITTEST_REGISTER_TESTER(CommonTests); + + /** + * @internal d-pointer class to be more flexible on future extension of the + * functionality without to much risk to break the binary compatibility. + */ + class CommonTests::Private + { + public: + + /** + * An KXMLGUIClient instance created on @a setUp() and + * passed to the @a KoMacro::Manager to bridge to the + * app-functionality. + */ + KXMLGUIClient* xmlguiclient; + + /** + * An @a TestObject instance used internaly to test + * handling and communication with from QObject + * inheritated instances. + */ + TestObject* testobject; + + TestAction* testaction; + + QDomDocument* doomdocument; + + /** + * Constructor. + */ + Private() + : xmlguiclient(0) + , testobject(0) + , testaction(0) + , doomdocument(0) + { + } + }; + +} + +typedef QValueList< KSharedPtr<KoMacro::MacroItem> >::size_type sizetype; + +CommonTests::CommonTests() + : KUnitTest::SlotTester() + , d( new Private() ) // create the private d-pointer instance. +{ +} + +CommonTests::~CommonTests() +{ + delete d->xmlguiclient; + delete d; +} + +void CommonTests::setUp() +{ + d->xmlguiclient = new KXMLGUIClient(); + ::KoMacro::Manager::init( d->xmlguiclient ); + + d->testobject = new TestObject( this ); + ::KoMacro::Manager::self()->publishObject("TestObject", d->testobject); + + d->testaction = new TestAction(); + ::KoMacro::Manager::self()->publishAction(d->testaction); + + d->doomdocument = new QDomDocument(); + + QString const xml = QString("<!DOCTYPE macros>" + + "<macro xmlversion=\"1\">" + + "<item action=\"testaction\" >" + "</item>" + "</macro>"); + + d->doomdocument->setContent(xml); +} + +void CommonTests::tearDown() +{ + delete d->doomdocument; + delete d->testobject; + delete d->xmlguiclient; +} + +void CommonTests::testManager() +{ + kdDebug()<<"===================== testManager() ======================" << endl; + + //check if manager-guiClient equals xmlguiclient + KOMACROTEST_ASSERT( ::KoMacro::Manager::self()->guiClient(), d->xmlguiclient ); + //check if manger-object equals testobject + KOMACROTEST_ASSERT( dynamic_cast<TestObject*>( (QObject*)::KoMacro::Manager::self()->object("TestObject") ), d->testobject ); +} +/* +void CommonTests::testAction() +{ + const QString testString = "teststring"; + const QString testInt = "testint"; + const QString testBool = "testbool"; + + //TODO: CLEANUP!!!!!! + //TODO: test manipulation of action and macroitem and context. + + kdDebug()<<"===================== testAction() ======================" << endl; + + //Publish TestAction for the first time. + + QDomElement const domelement = d->doomdocument->documentElement(); + + KSharedPtr<KoMacro::Macro> macro = KoMacro::Manager::self()->createMacro("testMacro"); + + //Is our XML parseable ? + KOMACROTEST_ASSERT(macro->parseXML(domelement),true); + + //?? + macro->execute(this); + + //create list of KSharedPtr from the childs of the macro + QValueList< KSharedPtr<KoMacro::MacroItem> >& items = macro->items(); + + + //check that there is one + KOMACROTEST_ASSERT( items.count(), sizetype(1) ); + //fetch the first one + KSharedPtr<KoMacro::Action> actionptr = items[0]->action(); + //How do we know that an action exist ? + //-> check that it is not null + KOMACROTEST_XASSERT(sizetype(actionptr.data()), sizetype(0)); + //fetch the "teststring"-variable + KSharedPtr<KoMacro::Variable> variableptr = actionptr->variable("teststring"); + //check that it is not null + KOMACROTEST_XASSERT(sizetype(variableptr.data()), sizetype(0)); + //check that it is " " + KOMACROTEST_ASSERT(variableptr->variant().toString(),QString("testString")); + + //fetch the "testint"-variable + variableptr = actionptr->variable("testint"); + //check that it is not null + KOMACROTEST_XASSERT(sizetype(variableptr.data()), sizetype(0)); + //check that it is " " + KOMACROTEST_ASSERT(variableptr->variant().toInt(),int(0)); + + //fetch the "testbool"-variable + variableptr = actionptr->variable("testbool"); + //check that it is not null + KOMACROTEST_XASSERT(sizetype(variableptr.data()), sizetype(0)); + //check that it is " " + KOMACROTEST_ASSERT(variableptr->variant().toBool(),true); + + actionptr->setVariable("teststring", "STRINGTEST", "TestString"); + variableptr = actionptr->variable("teststring"); + //check that it is not null + KOMACROTEST_XASSERT(sizetype(variableptr.data()), sizetype(0)); + //check that it is " " + KOMACROTEST_ASSERT(variableptr->variant().toString(),QString("TestString")); + + actionptr->setVariable("testint","INTTEST",INT_MAX); + variableptr = actionptr->variable("testint"); + KOMACROTEST_XASSERT(sizetype(variableptr.data()), sizetype(0)); + KOMACROTEST_ASSERT(sizetype(variableptr->variant().toInt()),sizetype(INT_MAX)); + + actionptr->setVariable("testbool","BOOLTEST", "false"); + variableptr = actionptr->variable("testbool"); + KOMACROTEST_XASSERT(sizetype(variableptr.data()), sizetype(0)); + KOMACROTEST_ASSERT(variableptr->variant().toBool(),false); + + //create new macroitem for testing + KoMacro::MacroItem* macroitem = new KoMacro::MacroItem(); + //set the action + macroitem->setAction(d->testaction); + //append the macroitem to testitems + items.append(macroitem); + //increased ?? + KOMACROTEST_ASSERT( items.count(), sizetype(2) ); + + //Manipulate the macroitem + macroitem->setVariable("teststring", "TeStString"); + variableptr = macroitem->variable("teststring"); + KOMACROTEST_XASSERT(sizetype(variableptr.data()), sizetype(0)); + KOMACROTEST_ASSERT(variableptr->variant().toString(),QString("TeStString")); + + macroitem->setVariable("testint",INT_MIN); + variableptr = macroitem->variable("testint"); + KOMACROTEST_XASSERT(sizetype(variableptr.data()), sizetype(0)); + KOMACROTEST_ASSERT(sizetype(variableptr->variant().toInt()),sizetype(INT_MIN)); + + macroitem->setVariable("testint",-1); + variableptr = macroitem->variable("testint"); + KOMACROTEST_XASSERT(sizetype(variableptr.data()), sizetype(0)); + KOMACROTEST_ASSERT(sizetype(variableptr->variant().toInt()),sizetype(-1)); + + + //commontests.cpp: In member function 'void KoMacroTest::CommonTests::testAction()': + //commontests.cpp:249: error: call of overloaded 'setVariable(const char [8], int)' is ambiguous + //../lib/macroitem.h:131: note: candidates are: QStringList KoMacro::MacroItem::setVariable(const QString&, KSharedPtr<KoMacro::Variable>) + //../lib/macroitem.h:137: note: QStringList KoMacro::MacroItem::setVariable(const QString&, const QVariant&) + + macroitem->setVariable("testint",(int) 0); + variableptr = macroitem->variable("testint"); + KOMACROTEST_XASSERT(sizetype(variableptr.data()), sizetype(0)); + KOMACROTEST_ASSERT(sizetype(variableptr->variant().toInt()),sizetype(0)); + + + macroitem->setVariable("testint",1); + variableptr = macroitem->variable("testint"); + KOMACROTEST_XASSERT(sizetype(variableptr.data()), sizetype(0)); + KOMACROTEST_ASSERT(sizetype(variableptr->variant().toInt()),sizetype(1)); + + macroitem->setVariable("testint",INT_MAX); + variableptr = macroitem->variable("testint"); + KOMACROTEST_XASSERT(sizetype(variableptr.data()), sizetype(0)); + KOMACROTEST_ASSERT(sizetype(variableptr->variant().toInt()),sizetype(INT_MAX)); + + macroitem->setVariable("testbool","false"); + variableptr = macroitem->variable("testbool"); + KOMACROTEST_XASSERT(sizetype(variableptr.data()), sizetype(0)); + KOMACROTEST_ASSERT(variableptr->variant().toBool(),false); + + //secondway for appending an macroitem + //add the manipulated macroitem + macro->addItem(macroitem); + //increased ?? + KOMACROTEST_ASSERT( items.count(), sizetype(3)); +} */ + +void CommonTests::testXmlhandler() +{ + kdDebug()<<"===================== testXmlhandler() ======================" << endl; + + // Local Init + KSharedPtr<KoMacro::Macro> macro = KoMacro::Manager::self()->createMacro("testMacro"); + QDomElement domelement; + + // Save old doomdocument + QString xmlOld = d->doomdocument->toString(); + + // Part 1: From XML to a Macro. + // Test-XML-document with normal allocated variables. + QString xml = QString("<!DOCTYPE macros>" + "<macro xmlversion=\"1\">" + "<item action=\"testaction\" >" + "<variable name=\"teststring\" >testString</variable>" + "<variable name=\"testint\" >0</variable>" + "<variable name=\"testbool\" >true</variable>" + "<variable name=\"testbla\" >somethingwrong</variable>" // TODO Is here a kdDebug-msg enough? + "<variable name=\"testdouble\" >0.6</variable>" + "</item>" + "</macro>"); + // Set the XML-document with the above string. + d->doomdocument->setContent(xml); + domelement = d->doomdocument->documentElement(); + //Is our XML parseable ? + KOMACROTEST_ASSERT(macro->parseXML(domelement),true); + + // Test-XML-document with bad root element. + xml = QString("<!DOCTYPE macros>" + "<maro xmlversion=\"1\">" + "<item action=\"testaction\" >" + "<variable name=\"teststring\" >testString</variable>" + "<variable name=\"testint\" >0</variable>" + "<variable name=\"testbool\" >true</variable>" + "<variable name=\"testdouble\" >0.6</variable>" + "</item>" + "</maro>"); + d->doomdocument->setContent(xml); + domelement = d->doomdocument->documentElement(); + KOMACROTEST_ASSERT(macro->parseXML(domelement),false); + + // Test-XML-document with wrong macro-xmlversion. + xml = QString("<!DOCTYPE macros>" + "<macro xmlversion=\"2\">" + "<item action=\"testaction\" >" + "<variable name=\"teststring\" >testString</variable>" + "<variable name=\"testint\" >0</variable>" + "<variable name=\"testbool\" >true</variable>" + "<variable name=\"testdouble\" >0.6</variable>" + "</item>" + "</macro>"); + d->doomdocument->setContent(xml); + domelement = d->doomdocument->documentElement(); + KOMACROTEST_ASSERT(macro->parseXML(domelement),false); + + // TODO Test-XML-document if it has a wrong structure like wrong parathesis + // or missing end tag (is this critical??). + /*xml = QString("<!DOCTYPE macros>" + "<macro xmlversion=\"1\">" + "<item action=\"testaction\" >" + "<variable name=\"teststring\" >testString</variable>" + "<variable name=\"testint\" >0</variable>" + "<variable name=\"testbool\" >true</variable>" + "</item>" + "</macro>"); + d->doomdocument->setContent(xml); + domelement = d->doomdocument->documentElement(); + KOMACROTEST_ASSERT(macro->parseXML(domelement),false);*/ + + // Test-XML-document with wrong item- and variable-tags. + // TODO Could this happen?? + xml = QString("<!DOCTYPE macros>" + "<macro xmlversion=\"1\">" + "<iem action=\"testaction\" >" + "<vle name=\"teststring\" >testString</variable>" + "<v name=\"testint\" >0</variable>" + "<variable name=\"testbool\" >true</variable>" + "<variable name=\"testdouble\" >0.6</variable>" + "</item>" + "</macro>"); + d->doomdocument->setContent(xml); + domelement = d->doomdocument->documentElement(); + KOMACROTEST_ASSERT(macro->parseXML(domelement),true); //should be false? + + // TODO Test-XML-document with maximum field-size. + xml = QString("<!DOCTYPE macros>" + "<macro xmlversion=\"1\">" + "<item action=\"testaction\" >" + "<variable name=\"teststring\" >testString</variable>" + "<variable name=\"testint\" > 0 </variable>" // the value should be INT_MAX + "<variable name=\"testbool\" >true</variable>" + "<variable name=\"testdouble\" >0.6</variable>" // DBL_MAX + "</item>" + "</macro>"); + d->doomdocument->setContent(xml); + domelement = d->doomdocument->documentElement(); + KOMACROTEST_ASSERT(macro->parseXML(domelement),true); + + // TODO Test-XML-document with minimum field-size. + xml = QString("<!DOCTYPE macros>" + "<macro xmlversion=\"1\">" + "<item action=\"testaction\" >" + "<variable name=\"teststring\" >testString</variable>" + "<variable name=\"testint\" >0</variable>" // INT_MIN + "<variable name=\"testbool\" >true</variable>" + "<variable name=\"testdouble\" >0.6</variable>" // DBL_MIN + "</item>" + "</macro>"); + d->doomdocument->setContent(xml); + domelement = d->doomdocument->documentElement(); + KOMACROTEST_ASSERT(macro->parseXML(domelement),true); + + // TODO Part 2: Read the parsen macro and make a comparison to the XML-document. + + // TODO Part 3: From a Macro to XML. + + // RODO Part 4: Compare the transformed XML with the given macro. + + // Set back xml-string for other tests. + d->doomdocument->setContent(xmlOld); +} + +void CommonTests::testFunction() +{ +//TODO: CLEANUP!!!!!! +/* + kdDebug()<<"===================== testFunction() ======================" << endl; + + //create a QDomDocument + QDomDocument domdocument = QDomDocument(); + //create some data + QString const comment = "Describe what the function does"; + QString const name = "myfunc"; + QString const text = "My Function"; + QString const receiver = "TestObject"; + QString const argument1 = "Some string"; + int const argument2 = 12345; + + //set "Function"-content in QDocument + domdocument.setContent(QString( + "<function name=\"" + name + "\" text=\"" + text + "\" comment=\"" + comment + "\" receiver=\"" + receiver + "\" slot=\"myslot(const QString & , int)\">" + "<argument>" + argument1 + "</argument>" + "<argument>" + QString("%1").arg(argument2) + "</argument>" + "</function>" + )); + + //create an KomacroFunction with our data, and put it into a KSharedPtr + KSharedPtr<KoMacro::Action> functionptr = ::KoMacro::Manager::self()->createAction( domdocument.documentElement() ); + //cast KSharedPtr to KoMacro-"Function" + KoMacro::Function* func = dynamic_cast<KoMacro::Function*>( functionptr.data() ); + //check that function is not null + KOMACROTEST_XASSERT((int) func, 0); + + //check domElement + KOMACROTEST_ASSERT( func->domElement(), domdocument.documentElement() ); + //check name + KOMACROTEST_ASSERT( QString(func->name()), name ); + //check text + KOMACROTEST_ASSERT( func->text(), text ); + //check comment + KOMACROTEST_ASSERT( func->comment(), comment ); + //check receiver + KOMACROTEST_ASSERT( func->receiver(), receiver ); + //check slot (arguments) + KOMACROTEST_ASSERT( QString(func->slot()), QString("myslot(const QString&,int)") ); + + //create KoMacro-MetaObject from receiverObject + KSharedPtr<KoMacro::MetaObject> receivermetaobject = func->receiverObject(); + //check that receivermetaobject.data is not null + KOMACROTEST_XASSERT((int) receivermetaobject.data(), 0); + + //create KoMacro-MetaMethod from receiverObject + KSharedPtr<KoMacro::MetaMethod> receivermetamethod = receivermetaobject->slot( func->slot().latin1() ); + //check that receivermetamethod.data is not null + KOMACROTEST_XASSERT((int) receivermetamethod.data(), 0); + + //create list of variables from func + KoMacro::Variable::List funcvariables = func->variables(); + //counter for hardcoded tests see below ... + uint i = 0; + KoMacro::Variable::List::ConstIterator it, end( funcvariables.constEnd() ); + for( it = funcvariables.constBegin(); it != end; ++it) { + kdDebug() << "VARIABLE => " << (*it ? (*it)->toString() : "<NULL>") << endl; + //hardcoded test: + // firstrun we have a QString, secondrun we have an int + switch(i) { + case 0: { // returnvalue + KOMACROTEST_ASSERT(*it, KSharedPtr<KoMacro::Variable>(NULL)); + } break; + case 1: { // first parameter + //check first variable of func is the same as argument1 + //QString const argument1 = "Some string"; + KOMACROTEST_ASSERT((*it)->toString(), argument1); + } break; + case 2: { // second parameter + //check second variable of func is the same as argument2 + //int const argument2 = 12345; + KOMACROTEST_ASSERT((*it)->toInt(), argument2); + } break; + default: { + } break; + } + i++; + } + + //check that we have two arguments + one returnvalue in func + KOMACROTEST_ASSERT( funcvariables.count(), uint(3) ); + + // check that the first argument (the returnvalue) is empty + KOMACROTEST_ASSERT( funcvariables[0], KSharedPtr<KoMacro::Variable>(NULL) ); + + //create a KoMacro-Variable-Ptr from first func argument + KSharedPtr<KoMacro::Variable> stringvar = funcvariables[1]; + //check that it is not null + KOMACROTEST_XASSERT((int) stringvar.data(),0); + //check via QVariant type that stringvar is from Type Variant + KOMACROTEST_ASSERT( stringvar->type(), KoMacro::MetaParameter::TypeVariant ); + //check via metaparameter that variant is from type string + KOMACROTEST_ASSERT( stringvar->variantType(), QVariant::String ); + //chech that stringvar equals argument1 + KOMACROTEST_ASSERT( stringvar->toString(), argument1 ); + + //create a KoMacro-Variable-Ptr from second func argument + KSharedPtr<KoMacro::Variable> intvar = funcvariables[2]; + //check that it is not null + KOMACROTEST_XASSERT((int) intvar.data(), 0); + //check via QVariant type that stringvar is from Type Variant + KOMACROTEST_ASSERT( intvar->type(), KoMacro::MetaParameter::TypeVariant ); + //check that intvar is An String -> we create an string from int because of xml + KOMACROTEST_ASSERT( intvar->variantType(), QVariant::String ); + //check that intvar equals argument2 + KOMACROTEST_ASSERT( intvar->toInt(), argument2 ); + + //returnvalue see testobject .... + KSharedPtr<KoMacro::Variable> funcreturnvalue = receivermetamethod->invoke( funcvariables ); + kdDebug() << "CommonTests::testFunction() RETURNVALUE =====> " << funcreturnvalue->toString() << endl; + KOMACROTEST_ASSERT( funcreturnvalue->toInt(), argument2 ); + + //check returnvalue + //func->setReturnValue(new KoMacro::Variable("54321")); + //KOMACROTEST_ASSERT( func->returnValue()->toString(), QString("54321") ); +*/ +} + +void CommonTests::testIntFunction() +{ +//TODO: CLEANUP!!!!!! +/* + kdDebug()<<"===================== testIntFunction() ======================" << endl; + + //create a QDomDocument + QDomDocument domdocument = QDomDocument(); + + //set "Function"-content in QDocument + domdocument.setContent(QString( + "<function name=\"myfunc\" text=\"My Function\" comment=\"comment\" receiver=\"TestObject\" slot=\"myslot(const QString & , int)\">" + "<argument>Some string</argument>" + "<argument>12345</argument>" + "</function>" + )); + + //create an KomacroFunction with our data, and put it into a KSharedPtr + KSharedPtr<KoMacro::Action> functionptr = ::KoMacro::Manager::self()->createAction( domdocument.documentElement() ); + //Cast data to function + KoMacro::Function* func = dynamic_cast<KoMacro::Function*>( functionptr.data() ); + //check that it is not null + KOMACROTEST_XASSERT((int) func, 0); + //execute the function + func->activate(); + //Check returnvalue is same value we entered + //KOMACROTEST_ASSERT(func->returnValue()->toString(),QString("12345")); +*/ +} + +void CommonTests::testDoubleFunction() +{ +//TODO: CLEANUP!!!!!! +/* + kdDebug()<<"===================== testDoubleFunction() ======================" << endl; + + //create a QDomDocument + QDomDocument domdocument = QDomDocument(); + + //set "Function"-content in QDocument + domdocument.setContent(QString( + "<function name=\"myfunc\" text=\"My Function\" comment=\"comment\" receiver=\"TestObject\" slot=\"myslot(const QString & , double)\">" + "<argument>Some string</argument>" + "<argument>12.56</argument>" + "</function>" + )); + + //create an KomacroFunction with our data, and put it into a KSharedPtr + KSharedPtr<KoMacro::Action> functionptr = ::KoMacro::Manager::self()->createAction( domdocument.documentElement() ); + //Cast data to function + KoMacro::Function* func = dynamic_cast<KoMacro::Function*>( functionptr.data() ); + //check that it is not null + KOMACROTEST_XASSERT((int) func, 0); + //execute the function + func->activate(); + //Check returnvalue is same value we entered + //KOMACROTEST_ASSERT(func->returnValue()->toString(),QString("12.56")); +*/ +} + +void CommonTests::testQStringFunction() +{ +//TODO: CLEANUP!!!!!! +/* + kdDebug()<<"===================== testQStringFunction() ======================" << endl; + + //create a QDomDocument + QDomDocument domdocument = QDomDocument(); + + //set "Function"-content in QDocument + domdocument.setContent(QString( + "<function name=\"myfunc\" text=\"My Function\" comment=\"comment\" receiver=\"TestObject\" slot=\"myslot(const QString &)\">" + "<argument>Some string</argument>" + "</function>" + )); + + //create an KomacroFunction with our data, and put it into a KSharedPtr + KSharedPtr<KoMacro::Action> functionptr = ::KoMacro::Manager::self()->createAction( domdocument.documentElement() ); + //Cast data to function + KoMacro::Function* func = dynamic_cast<KoMacro::Function*>( functionptr.data() ); + //check that it is not null + KOMACROTEST_XASSERT((int) func, 0); + //execute the function func->activate(); + //Check returnvalue is same value we entered + //KOMACROTEST_ASSERT(func->returnValue()->toString(),QString("Some string")); +*/ +} + +void CommonTests::testMacro() +{ +//TODO: CLEANUP!!!!!! + kdDebug()<<"===================== testMacro() ======================" << endl; + + QDomElement const domelement = d->doomdocument->documentElement(); + + KSharedPtr<KoMacro::Macro> macro = KoMacro::Manager::self()->createMacro("testMacro"); + //Is our XML parseable ? + KOMACROTEST_ASSERT(macro->parseXML(domelement),true); + +// //create a QDomDocument +// QDomDocument domdocument = QDomDocument(); +// +// //Fully fleged content this time with macro,function and action +// domdocument.setContent(QString( +// "<macro name=\"mymacro\" icon=\"myicon\" text=\"My Macro\" comment=\"Some comment to describe the Macro.\">" +// "<action name=\"myaction\" text=\"My Action\" comment=\"Just some comment\" />" +// "<function name=\"myfunc\" text=\"My Function\" comment=\"Describe what the function does\" receiver=\"TestObject\" slot=\"myslot(const QString &)\">" +// "<argument>The myfunc argument string</argument>" +// "</function>" +// "</macro>" +// )); +// +// //create Macro +// // KSharedPtr<KoMacro::Action> macroptr = ::KoMacro::Manager::self()->createAction( domdocument.documentElement() ); +// //cast data to Macro +// KoMacro::Macro* macro = dynamic_cast<KoMacro::Macro*>( macroptr.data() ); + //check that it is not null + KOMACROTEST_XASSERT(sizetype(macro.data()), sizetype(0)); + //check that domeElement given to manager is the sam as in the macro +// KOMACROTEST_ASSERT( macro->toXML(), d->doomdocument->documentElement() ); + //check the name + KOMACROTEST_ASSERT( QString(macro->name()), QString("testMacro") ); + + /** + @deprecated values no longer exist + + //check the text + KOMACROTEST_ASSERT( macro->text(), QString("My Macro") ); + //check iconname + KOMACROTEST_ASSERT( QString(macro->icon()), QString("myicon") ); + //check comment + KOMACROTEST_ASSERT( macro->comment(), QString("Some comment to describe the Macro.") ); + */ + + //create list of KsharedPtr from the childs of the macro + QValueList< KSharedPtr<KoMacro::MacroItem> >& items = macro->items(); + //check that there is one + KOMACROTEST_ASSERT( items.count(), sizetype(1) ); + //fetch the first one + KSharedPtr<KoMacro::Action> actionptr = items[0]->action(); + //How do we know that an action exist ? + //-> check that it is not null + KOMACROTEST_XASSERT(sizetype(actionptr.data()), sizetype(0)); + //check that it has the right name + KOMACROTEST_ASSERT( QString(actionptr->name()), QString("testaction") ); + //check that it has the right text + KOMACROTEST_ASSERT( actionptr->text(), QString("Test") ); + //check that it has the right comment +// KOMACROTEST_ASSERT( actionptr->comment(), QString("") ); +/* + //fetch the second one + KSharedPtr<KoMacro::Action> myfuncptr = children[1]; + //cast it to function + + KoMacro::Function* myfunc = dynamic_cast<KoMacro::Function*>( myfuncptr.data() ); + //check that it isn?t null + KOMACROTEST_XASSERT((int) myfunc, 0); + + //check it?s name + KOMACROTEST_ASSERT( QString(myfunc->name()), QString("myfunc")); + + //check it?s text + KOMACROTEST_ASSERT( myfunc->text(), QString("My Function") ); + //check it?s comment + KOMACROTEST_ASSERT( myfunc->comment(), QString("Describe what the function does") ); + //check it?s receiver object + KOMACROTEST_ASSERT( myfunc->receiver(), QString("TestObject") ); + //check it?s slot + KOMACROTEST_ASSERT( myfunc->slot(), QString("myslot(const QString&)") ); + + //exceute it + myfunc->activate(); +*/ + //create another macro + KSharedPtr<KoMacro::Macro> yanMacro = KoMacro::Manager::self()->createMacro("testMacro2"); + + KOMACROTEST_ASSERT(yanMacro->parseXML(domelement),true); + //create two more macros + //KSharedPtr<KoMacro::Action> yanActionptr2 = ::KoMacro::Manager::self()->createAction( domdocument.documentElement() ); + //KSharedPtr<KoMacro::Action> yanActionptr3 = ::KoMacro::Manager::self()->createAction( domdocument.documentElement() ); + + //check that they aren?t null + KOMACROTEST_XASSERT(sizetype(yanMacro.data()), sizetype(0)); + //KOMACROTEST_XASSERT((int) yanActionptr2.data(), 0); + //KOMACROTEST_XASSERT((int) yanActionptr3.data(), 0); + + //create a list of the children from yanMacro + //QValueList< KSharedPtr<KoMacro::Action> > yanChildren = yanMacro->children(); + //check that there are two + //KOMACROTEST_ASSERT(yanChildren.count(), uint(2)); +/* + { + //keep oldsize + const int oldsize = yanChildren.count(); + //add a new child to the macro + yanMacro->addChild(yanActionptr2); + //get the children + yanChildren = yanMacro->children(); + //get count of children + const int size = yanChildren.count(); + //check count has changed + KOMACROTEST_XASSERT(size, oldsize); + } + + { + //keep oldsize + const int oldsize = yanChildren.count(); + //add a new child to the macro + yanMacro->addChild(yanActionptr3); + //get the children + yanChildren = yanMacro->children(); + //get count of children + const int size = yanChildren.count(); + //check count has changed + KOMACROTEST_XASSERT(size, oldsize); + //check the hasChildren function + KOMACROTEST_ASSERT(yanMacro->hasChildren(), true); + } +*/ + +} + +void CommonTests::testDom() { +//TODO: CLEANUP!!!!!! + kdDebug()<<"===================== testDom() ======================" << endl; +/* + //create a QDomDocument + QDomDocument domdocument = QDomDocument(); + //create data for various documents + QString const comment = "Describe what the function does"; + QString const name = "myfunc"; + QString const text = "My Function"; + QString const receiver1 = "TestObject"; + QString const receiver2 = "GibtsNich"; + + //create wrong Argument tag + domdocument.setContent(QString( + "<function name=\"" + name + "\" text=\"" + text + "\" comment=\"" + comment + "\" receiver=\"" + receiver1 + "\" slot=\"myslot(const QString & , int)\">" + "<Arg0ment>Some string</Arg0ment>" + "<Arg0ment>12345</Arg0ment>" + "</function>" + )); + //create functiom + KSharedPtr<KoMacro::Action> macroptr = ::KoMacro::Manager::self()->createAction( domdocument.documentElement() ); + //try to execute function and catch exception + KOMACROTEST_ASSERTEXCEPTION(KoMacro::Exception&, macroptr->activate()); + + //create wrong receiver + domdocument.setContent(QString( + "<function name=\"" + name + "\" text=\"" + text + "\" comment=\"" + comment + "\" receiver=\"" + receiver2 + "\" slot=\"myslot(const QString & , int)\">" + "<argument>Some string</argument>" + "<argument>12345</argument>" + "</function>" + )); + //create function + macroptr = ::KoMacro::Manager::self()->createAction( domdocument.documentElement() ); + //try to execute function and catch exception + KOMACROTEST_ASSERTEXCEPTION(KoMacro::Exception&, macroptr->activate()); + + //create "wrong" number of parameters + domdocument.setContent(QString( + "<function name=\"" + name + "\" text=\"" + text + "\" comment=\"" + comment + "\" receiver=\"" + receiver1 + "\" slot=\"myslot(const QString & , int, double)\">" + "<argument>Some string</argument>" + "<argument>12345</argument>" + "<argument>12345.25</argument>" + "</function>" + )); + //create function + macroptr = ::KoMacro::Manager::self()->createAction( domdocument.documentElement() ); + //try to execute function and catch exception + KOMACROTEST_ASSERTEXCEPTION(KoMacro::Exception&, macroptr->activate()); + + //create wrong function tag + domdocument.setContent(QString( + "<NoFunction name=\"" + name + "\" text=\"" + text + "\" comment=\"" + comment + "\" receiver=\"" + receiver1 + "\" slot=\"myslot(const QString & , int, double)\">" + "<argument>Some string</argument>" + "<argument>12345</argument>" + "<argument>12345.25</argument>" + "</NoFunction>" + )); + //try to create function and catch exception + KOMACROTEST_ASSERTEXCEPTION(KoMacro::Exception&, macroptr = ::KoMacro::Manager::self()->createAction( domdocument.documentElement() )); + + //create empty function + domdocument.setContent(QString( + "<function name=\"\" text=\"\" comment=\"\" receiver=\"\" slot=\"\">" + "<argument> </argument>" + "<argument> </argument>" + "<argument> </argument>" + "</function>" + )); + //create function + macroptr = ::KoMacro::Manager::self()->createAction( domdocument.documentElement() ); + //try to execute function and catch exception + KOMACROTEST_ASSERTEXCEPTION(KoMacro::Exception&, macroptr->activate()); + + + //create empty function + domdocument.setContent(QString( + "<function>" + "</function>" + )); + //create function + macroptr = ::KoMacro::Manager::self()->createAction( domdocument.documentElement() ); + //try to execute function and catch exception + KOMACROTEST_ASSERTEXCEPTION(KoMacro::Exception&, macroptr->activate()); +*/ +} + +void CommonTests::testVariables() +{ +//TODO: CLEANUP!!!!!! + kdDebug()<<"===================== testVariables() ======================" << endl; +/* + //create a QDomDocument + QDomDocument domdocument = QDomDocument(); + //create data + domdocument.setContent(QString( + "<macro name=\"mymacro123\" text=\"My Macro 123\">" + "<function name=\"func1\" text=\"Function1\" receiver=\"TestObject\" slot=\"myslot(const QString &)\" >" + "<argument>$MyArgumentVariable</argument>" + "<return>$MyReturnVariable</return>" + "</function>" + "</macro>" + )); + + //create an macro + KSharedPtr<KoMacro::Action> macroptr = ::KoMacro::Manager::self()->createAction( domdocument.documentElement() ); + //cast data to macro + KoMacro::Macro* macro = dynamic_cast<KoMacro::Macro*>( macroptr.data() ); + //check that it is not null + KOMACROTEST_XASSERT((int) macro, 0); + + //create a list of its children + QValueList< KSharedPtr<KoMacro::Action> > children = macro->children(); + //Check that there are two children. The first child is always the returnvalue. + KOMACROTEST_ASSERT( children.count(), uint(2) ); + //fetch the children + KSharedPtr<KoMacro::Action> func1ptr = children[1]; + + //create new context + KSharedPtr<KoMacro::Context> context = new KoMacro::Context(macroptr); + + { + //try to execute function with non-functional variable + KOMACROTEST_ASSERTEXCEPTION(KoMacro::Exception&, func1ptr->activate(context)); + + KOMACROTEST_ASSERTEXCEPTION(KoMacro::Exception&, context->variable("$MyReturnVariable333")); + } + + { + //set variable to be a QString + context->setVariable("$MyArgumentVariable", new KoMacro::Variable("Some string")); + //execute function + func1ptr->activate(context); + //fetch return value + KSharedPtr<KoMacro::Variable> returnvariable = context->variable("$MyReturnVariable"); + //check that it is not null + KOMACROTEST_XASSERT( (int) returnvariable.data(), 0); + //check that it is "Some String" + KOMACROTEST_ASSERT(returnvariable->toString(),QString("Some string")); + } + + { + //set variable to be an Int + context->setVariable("$MyArgumentVariable", new KoMacro::Variable( 12345 )); + //execute function + func1ptr->activate(context); + //fetch return value + KSharedPtr<KoMacro::Variable> returnvariable = context->variable("$MyReturnVariable"); + //check that it is not null + KOMACROTEST_XASSERT( (int) returnvariable.data(), 0); + //check that it is 12345 + KOMACROTEST_ASSERT(returnvariable->toInt(),12345); + } +*/ +} + +#include "commontests.moc" diff --git a/kexi/plugins/macros/tests/commontests.h b/kexi/plugins/macros/tests/commontests.h new file mode 100644 index 00000000..a3199ce2 --- /dev/null +++ b/kexi/plugins/macros/tests/commontests.h @@ -0,0 +1,118 @@ +/*************************************************************************** + * This file is part of the KDE project + * copyright (C) 2005 by Sebastian Sauer (mail@dipe.org) + * copyright (C) 2005 by Tobi Krebs (tobi.krebs@gmail.com) + * + * 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 KOMACROTEST_COMMONTESTS_H +#define KOMACROTEST_COMMONTESTS_H + +#include <kunittest/tester.h> + +namespace KoMacroTest { + + /** + * The common testsuite used to test common @a KoMacro + * functionality. + */ + class CommonTests : public KUnitTest::SlotTester + { + Q_OBJECT + public: + + /** + * Constructor. + */ + CommonTests(); + + /** + * Destructor. + */ + virtual ~CommonTests(); + + public slots: + + /** + * This slot got called by KUnitTest before testing + * starts. + */ + void setUp(); + + /** + * This slot got called by KUnitTest after all tests + * are done. + */ + void tearDown(); + + /** + * Test the @a KoMacro::Manager functionality. + */ + void testManager(); + + /** + * Test the @a KoMacro::Action functionality. + */ + //void testAction(); + + /** + * Test the @a KoMacro::XmlHandler functionality. + */ + void testXmlhandler(); + + /** + * Test the @a KoMacro::Function functionality. + */ + void testFunction(); + + /** + * Test the @a KoMacro::Function functionality with an int. + */ + void testIntFunction(); + + /** + * Test the @a KoMacro::Function functionality with a double. + */ + void testDoubleFunction(); + + /** + * Test the @a KoMacro::Function functionality with a QString. + */ + void testQStringFunction(); + + /** + * Test the @a KoMacro::Macro functionality. + */ + void testMacro(); + + /** + * Test the @a KoMacro::Dom functionality. + */ + void testDom(); + /** + * Test the @a KoMacro::Variable functionality. + */ + void testVariables(); + + private: + /// @internal d-pointer class. + class Private; + /// @internal d-pointer instance. + Private* const d; + }; + +} + +#endif diff --git a/kexi/plugins/macros/tests/komacrotest.cpp b/kexi/plugins/macros/tests/komacrotest.cpp new file mode 100644 index 00000000..55d017a9 --- /dev/null +++ b/kexi/plugins/macros/tests/komacrotest.cpp @@ -0,0 +1,58 @@ +/*************************************************************************** + * This file is part of the KDE project + * copyright (C) 2005 by Sebastian Sauer (mail@dipe.org) + * copyright (C) 2005 by Tobi Krebs (tobi.krebs@gmail.com) + * 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 <kaboutdata.h> +#include <kapplication.h> +#include <kcmdlineargs.h> +#include <klocale.h> +#include <kunittest/runner.h> + +static const char description[] = + I18N_NOOP("KoMacroTester"); +static const char version[] = "0.1"; +static KCmdLineOptions options[] = +{ + KCmdLineLastOption +}; + +int main( int argc, char** argv ) +{ + try { + KAboutData about("KoMacroTester", + I18N_NOOP("KoMacroTester"), + version, + description, + KAboutData::License_LGPL, + "(C) 2005 Sebastian Sauer", 0, 0, "mail@dipe.org"); + + KCmdLineArgs::init(argc, argv, &about); + KCmdLineArgs::addCmdLineOptions( options ); + KApplication app; + + //create an new "Console"-runner + KUnitTest::Runner * runner = KUnitTest::Runner::self(); + //start our Testsuite + runner->runTests(); + //done + return 0; + } + // mmh seems we forgot to catch an exception... + catch(...) { + qFatal("Unhandled Exception!"); + } +} diff --git a/kexi/plugins/macros/tests/komacrotestbase.h b/kexi/plugins/macros/tests/komacrotestbase.h new file mode 100644 index 00000000..d423e086 --- /dev/null +++ b/kexi/plugins/macros/tests/komacrotestbase.h @@ -0,0 +1,90 @@ +/*************************************************************************** + * This file is part of the KDE project + * copyright (C) 2005 by Sebastian Sauer (mail@dipe.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 KOMACROTEST_BASE_H +#define KOMACROTEST_BASE_H + +//Our own extended Macros from KUnittest +/** +* Macro to perform an equality check and exits the method if the check failed. +* +* @param actual The actual value. +* @param expected The expected value. +*/ +#define KOMACROTEST_ASSERT(actual, expected) \ + { \ + std::cout << QString("Testing: %1 == %2").arg(#actual).arg(#expected).latin1() << std::endl; \ + check( __FILE__, __LINE__, #actual, actual, expected, false ); \ + if(actual != expected) \ + { \ + kdWarning() << QString("==============> FAILED") << endl; \ + return; \ + } \ + } + +/** +* Macro to perform a check that is expected to fail and that exits the method if the check failed. +* +* @param actual The actual value. +* @param notexpected The not expected value. +*/ +#define KOMACROTEST_XASSERT(actual, notexpected) \ + { \ + std::cout << QString("Testing: %1 != %2").arg(#actual).arg(#notexpected).latin1() << std::endl; \ + check( __FILE__, __LINE__, #actual, actual, notexpected, true ); \ + if(actual == notexpected) \ + { \ + kdWarning() << QString("==============> FAILED") << endl; \ + return; \ + } \ + } + +/** +* Macro to test that @p expression throws an exception that is catched with the +* @p exceptionCatch exception. +* +* @param exceptionCatch The exception that is expected to be thrown. +* @param expression The expression that is executed within a try-catch block to +* check for the @p exceptionCatch . +*/ +#define KOMACROTEST_ASSERTEXCEPTION(exceptionCatch, expression) \ + { \ + try { \ + expression; \ + } \ + catch(exceptionCatch) { \ + setExceptionRaised(true); \ + } \ + if(exceptionRaised()) { \ + success(QString(__FILE__) + "[" + QString::number(__LINE__) + "]: passed " + #expression); \ + setExceptionRaised(false); \ + } \ + else { \ + failure(QString(__FILE__) + "[" + QString::number(__LINE__) + QString("]: failed to throw an exception on: ") + #expression); \ + return; \ + } \ + } + +#endif + +//Used more tha once at various places +//names of variables from testaction +namespace KoMacroTest { + static const QString TESTSTRING = "teststring"; + static const QString TESTINT = "testint"; + static const QString TESTBOOL = "testbool"; +} diff --git a/kexi/plugins/macros/tests/komacrotestgui.cpp b/kexi/plugins/macros/tests/komacrotestgui.cpp new file mode 100644 index 00000000..abf4459d --- /dev/null +++ b/kexi/plugins/macros/tests/komacrotestgui.cpp @@ -0,0 +1,60 @@ +/*************************************************************************** + * This file is part of the KDE project + * copyright (C) 2005 by Sebastian Sauer (mail@dipe.org) + * copyright (C) 2005 by Tobi Krebs (tobi.krebs@gmail.com) + * 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 <kaboutdata.h> +#include <kapplication.h> +#include <kcmdlineargs.h> +#include <klocale.h> + +#include "kunittest/runnergui.h" + +static const char description[] = + I18N_NOOP("KoMacroTestgui."); + +static const char version[] = "0.1"; + +static const KCmdLineOptions options[] = +{ + KCmdLineLastOption +}; + +int main( int argc, char** argv ) +{ + try { + KAboutData const about("KomacroTests", I18N_NOOP("KomacroTests"), version, description, + KAboutData::License_LGPL, "(C) 2005 Tobi Krebs", 0, 0, + "Tobi.Krebs@gmail.com"); + + KCmdLineArgs::init(argc, argv, &about); + KCmdLineArgs::addCmdLineOptions( options ); + //create new kapplication + KApplication app; + //create new kunitrunnergui + KUnitTest::RunnerGUI runner(0); + //show the ui + runner.show(); + //set ui mainwidget + app.setMainWidget(&runner); + //return exitcode of ui + return app.exec(); + } + // mmh seems we forgot to catch an exception... + catch(...) { + qFatal("Unhandled Exception!"); + } +} diff --git a/kexi/plugins/macros/tests/macroitemtests.cpp b/kexi/plugins/macros/tests/macroitemtests.cpp new file mode 100644 index 00000000..366318e1 --- /dev/null +++ b/kexi/plugins/macros/tests/macroitemtests.cpp @@ -0,0 +1,243 @@ +/*************************************************************************** + * This file is part of the KDE project + * copyright (C) 2006 by Sebastian Sauer (mail@dipe.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 "macroitemtests.h" +#include "testaction.h" +#include "komacrotestbase.h" + +#include "../lib/action.h" +#include "../lib/manager.h" +#include "../lib/macro.h" +#include "../lib/variable.h" +#include "../lib/metaobject.h" +#include "../lib/metamethod.h" +#include "../lib/metaparameter.h" +#include "../lib/exception.h" +#include "../lib/macroitem.h" + +#include <ostream> + +#include <qstringlist.h> +#include <qdom.h> + +#include <kdebug.h> +#include <kunittest/runner.h> +#include <kxmlguiclient.h> + +using namespace KUnitTest; +using namespace KoMacroTest; + +namespace KoMacroTest { + + /** + * Register KoMacroTest::CommonTests as TestSuite. + */ + + KUNITTEST_SUITE("KoMacroTestSuite"); + KUNITTEST_REGISTER_TESTER(MacroitemTests); + + + class MacroitemTests::Private + { + public: + /** + * An KXMLGUIClient instance created on @a setUp() and + * passed to the @a KoMacro::Manager to bridge to the + * app-functionality. + */ + KXMLGUIClient* xmlguiclient; + + /** + * An @a TestObject instance used internaly to test + * handling and communication with from QObject + * inheritated instances. + */ + TestAction* testaction; + + QDomDocument* doomdocument; + + KSharedPtr<KoMacro::Macro> macro; + + Private() + : xmlguiclient(0) + , testaction(0) + , doomdocument(0) + , macro(0) + { + } + }; +} + +typedef QValueList< KSharedPtr<KoMacro::MacroItem> >::size_type sizetype; + +MacroitemTests::MacroitemTests() + : KUnitTest::SlotTester() + , d( new Private() ) // create the private d-pointer instance. +{ +} + +MacroitemTests::~MacroitemTests() +{ + delete d->xmlguiclient; + delete d; +} + +void MacroitemTests::setUp() +{ + d->xmlguiclient = new KXMLGUIClient(); + + if (::KoMacro::Manager::self() == 0) { + ::KoMacro::Manager::init( d->xmlguiclient ); + } + + d->testaction = new TestAction(); + ::KoMacro::Manager::self()->publishAction(d->testaction); + + d->doomdocument = new QDomDocument(); + + QString const xml = QString("<!DOCTYPE macros>" + "<macro xmlversion=\"1\" >" + "<item action=\"testaction\" >" + "</item>" + "</macro>"); + + d->doomdocument->setContent(xml); + d->macro = KoMacro::Manager::self()->createMacro("testMacro"); + d->macro->parseXML(d->doomdocument->documentElement()); + d->macro->execute(this); +} + +void MacroitemTests::tearDown() +{ + delete d->macro; + delete d->doomdocument; + delete d->xmlguiclient; +} + + +void MacroitemTests::testMacro() +{ + kdDebug()<<"===================== testMacroitem() ======================" << endl; + kdDebug()<<"===================== testMacro() ======================" << endl; + + //fetch Items and .. + QValueList< KSharedPtr<KoMacro::MacroItem> >& items = d->macro->items(); + + //... check that there is one + KOMACROTEST_XASSERT( items.count(), sizetype(0) ); +} + +void MacroitemTests::testMacroItemString() +{ + + + kdDebug()<<"===================== testMacroItemString() ======================" << endl; + + QValueList< KSharedPtr<KoMacro::MacroItem> >& items = d->macro->items(); + KSharedPtr<KoMacro::Action> actionptr = items[0]->action(); + KSharedPtr<KoMacro::Variable> variableptr = actionptr->variable(TESTSTRING); + + //create new macroitem for testing + KoMacro::MacroItem* macroitem = new KoMacro::MacroItem(); + //set the action + macroitem->setAction(d->testaction); + + //append the macroitem to testitems + items.append(macroitem); + + //increased ?? + KOMACROTEST_ASSERT( items.count(), sizetype(2) ); + + //Manipulate the macroitem + macroitem->setVariable(TESTSTRING, "TeStString"); + variableptr = macroitem->variable(TESTSTRING); + KOMACROTEST_XASSERT(sizetype(variableptr.data()), sizetype(0)); + KOMACROTEST_ASSERT(variableptr->variant().toString(),QString("TeStString")); + + + //secondway for appending an macroitem + //add the manipulated macroitem + d->macro->addItem(macroitem); + + //increased ?? + KOMACROTEST_ASSERT( items.count(), sizetype(3)); + +} + +void MacroitemTests::testMacroItemInt() +{ + + + kdDebug()<<"===================== testMacroItemInt() ======================" << endl; + + QValueList< KSharedPtr<KoMacro::MacroItem> >& items = d->macro->items(); + KSharedPtr<KoMacro::Action> actionptr = items[0]->action(); + + //create new macroitem for testing + KoMacro::MacroItem* macroitem = new KoMacro::MacroItem(); + //set the action + macroitem->setAction(d->testaction); + items.append(macroitem); + + macroitem->setVariable(TESTINT,INT_MIN); + KSharedPtr<KoMacro::Variable> variableptr = macroitem->variable(TESTINT); + KOMACROTEST_XASSERT(sizetype(variableptr.data()), sizetype(0)); + KOMACROTEST_ASSERT(sizetype(variableptr->variant().toInt()),sizetype(INT_MIN)); + + macroitem->setVariable(TESTINT,-1); + variableptr = macroitem->variable(TESTINT); + KOMACROTEST_XASSERT(sizetype(variableptr.data()), sizetype(0)); + KOMACROTEST_ASSERT(sizetype(variableptr->variant().toInt()),sizetype(-1)); + + macroitem->setVariable(TESTINT,QVariant(0)); + variableptr = macroitem->variable(TESTINT); + KOMACROTEST_XASSERT(sizetype(variableptr.data()), sizetype(0)); + KOMACROTEST_ASSERT(sizetype(variableptr->variant().toInt()),sizetype(0)); + + macroitem->setVariable(TESTINT,1); + variableptr = macroitem->variable(TESTINT); + KOMACROTEST_XASSERT(sizetype(variableptr.data()), sizetype(0)); + KOMACROTEST_ASSERT(sizetype(variableptr->variant().toInt()),sizetype(1)); + + macroitem->setVariable(TESTINT,INT_MAX); + variableptr = macroitem->variable(TESTINT); + KOMACROTEST_XASSERT(sizetype(variableptr.data()), sizetype(0)); + KOMACROTEST_ASSERT(sizetype(variableptr->variant().toInt()),sizetype(INT_MAX)); +} + +void MacroitemTests::testMacroItemBool() +{ + + + kdDebug()<<"===================== testMacroItemBool() ======================" << endl; + + QValueList< KSharedPtr<KoMacro::MacroItem> >& items = d->macro->items(); + KSharedPtr<KoMacro::Action> actionptr = items[0]->action(); + + //create new macroitem for testing + KoMacro::MacroItem* macroitem = new KoMacro::MacroItem(); + //set the action + macroitem->setAction(d->testaction); + items.append(macroitem); + + macroitem->setVariable(TESTBOOL,"false"); + KSharedPtr<KoMacro::Variable> variableptr = macroitem->variable(TESTBOOL); + KOMACROTEST_XASSERT(sizetype(variableptr.data()), sizetype(0)); + KOMACROTEST_ASSERT(variableptr->variant().toBool(),false); +} +#include "macroitemtests.moc" diff --git a/kexi/plugins/macros/tests/macroitemtests.h b/kexi/plugins/macros/tests/macroitemtests.h new file mode 100644 index 00000000..3e44eebd --- /dev/null +++ b/kexi/plugins/macros/tests/macroitemtests.h @@ -0,0 +1,87 @@ +/*************************************************************************** + * This file is part of the KDE project + * copyright (C) 2005 by Sebastian Sauer (mail@dipe.org) + * copyright (C) 2005 by Tobi Krebs (tobi.krebs@gmail.com) + * + * 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 KOMACROTEST_ACTIONTESTS_H +#define KOMACROTEST_ACTIONTESTS_H + +#include <kunittest/tester.h> + +namespace KoMacroTest { + + /** + * The common testsuite used to test common @a KoMacro + * functionality. + */ + class MacroitemTests : public KUnitTest::SlotTester + { + Q_OBJECT + public: + + /** + * Constructor. + */ + MacroitemTests(); + + /** + * Destructor. + */ + virtual ~MacroitemTests(); + + public slots: + + /** + * This slot got called by KUnitTest before testing + * starts. + */ + void setUp(); + + /** + * This slot got called by KUnitTest after all tests + * are done. + */ + void tearDown(); + + /** + * Subtest for the @a KoMacro::Action functionality. + */ + void testMacro(); + + /** + * Subtest for the @a KoMacro::Action functionality. + */ + void testMacroItemString(); + /** + * Subtest for the @a KoMacro::Action functionality. + */ + void testMacroItemInt(); + /** + * Subtest for the @a KoMacro::Action functionality. + */ + void testMacroItemBool(); + + private: + /// @internal d-pointer class. + class Private; + /// @internal d-pointer instance. + Private* const d; + }; + +} + +#endif diff --git a/kexi/plugins/macros/tests/macrotests.cpp b/kexi/plugins/macros/tests/macrotests.cpp new file mode 100644 index 00000000..ed222df2 --- /dev/null +++ b/kexi/plugins/macros/tests/macrotests.cpp @@ -0,0 +1,192 @@ +/*************************************************************************** + * This file is part of the KDE project + * copyright (C) 2006 by Sebastian Sauer (mail@dipe.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 "macrotests.h" +#include "testobject.h" +#include "testaction.h" +#include "komacrotestbase.h" + +#include "../lib/action.h" +#include "../lib/function.h" +#include "../lib/manager.h" +#include "../lib/macro.h" +#include "../lib/variable.h" +#include "../lib/metaobject.h" +#include "../lib/metamethod.h" +#include "../lib/metaparameter.h" +#include "../lib/exception.h" +#include "../lib/macroitem.h" + +#include <ostream> + +#include <qstringlist.h> +#include <qdom.h> + +#include <kdebug.h> +#include <kunittest/runner.h> +#include <kxmlguiclient.h> + +using namespace KUnitTest; +using namespace KoMacroTest; + +namespace KoMacroTest { + + /** + * Register KoMacroTest::CommonTests as TestSuite. + */ + KUNITTEST_SUITE("KoMacroTestSuite") + KUNITTEST_REGISTER_TESTER(MacroTests); + + class MacroTests::Private + { + public: + /** + * An KXMLGUIClient instance created on @a setUp() and + * passed to the @a KoMacro::Manager to bridge to the + * app-functionality. + */ + KXMLGUIClient* xmlguiclient; + + /** + * An @a TestObject instance used internaly to test + * handling and communication with from QObject + * inheritated instances. + */ + TestObject* testobject; + + TestAction* testaction; + + QDomDocument* doomdocument; + + Private() + : xmlguiclient(0) + , testobject(0) + , testaction(0) + , doomdocument(0) + { + } + }; +} + +typedef QValueList< KSharedPtr<KoMacro::MacroItem> >::size_type sizetype; + + +MacroTests::MacroTests() + : KUnitTest::SlotTester() + , d( new Private() ) // create the private d-pointer instance. +{ +} + +MacroTests::~MacroTests() +{ + delete d->xmlguiclient; + delete d; +} + + +void MacroTests::setUp() +{ + d->xmlguiclient = new KXMLGUIClient(); + //::KoMacro::Manager::init( d->xmlguiclient ); + if (::KoMacro::Manager::self() == 0) { + ::KoMacro::Manager::init( d->xmlguiclient ); + } + d->testobject = new TestObject( this ); + ::KoMacro::Manager::self()->publishObject("TestObject", d->testobject); + + d->testaction = new TestAction(); + ::KoMacro::Manager::self()->publishAction(d->testaction); + + d->doomdocument = new QDomDocument(); + + QString const xml = QString("<!DOCTYPE macros>" + "<macro xmlversion=\"1\" >" + "<item action=\"testaction\" >" + "</item>" + "</macro>"); + + d->doomdocument->setContent(xml); +} + +void MacroTests::tearDown() +{ + delete d->doomdocument; + delete d->testobject; + delete d->xmlguiclient; +} + +void MacroTests::testMacro() +{ + kdDebug()<<"===================== testMacro() ======================" << endl; + + QDomElement const domelement = d->doomdocument->documentElement(); + + KSharedPtr<KoMacro::Macro> macro1 = KoMacro::Manager::self()->createMacro("testMacro"); + KSharedPtr<KoMacro::Macro> macro2 = KoMacro::Manager::self()->createMacro("testMacro"); + //Is our XML parseable ? + KOMACROTEST_ASSERT(macro1->parseXML(domelement),true); + KOMACROTEST_ASSERT(macro2->parseXML(domelement),true); + + //check that it is not null + KOMACROTEST_XASSERT(sizetype(macro1.data()), sizetype(0)); + KOMACROTEST_XASSERT(sizetype(macro2.data()), sizetype(0)); + + //check macro1 == macro2 + KOMACROTEST_ASSERT(macro1->name(), macro2->name() ); + + //create list of KsharedPtr from the childs of the macro + QValueList< KSharedPtr<KoMacro::MacroItem> >& items1 = macro1->items(); + QValueList< KSharedPtr<KoMacro::MacroItem> >& items2 = macro2->items(); + + //check that there is one + KOMACROTEST_XASSERT( items1.count(), sizetype(0) ); + KOMACROTEST_XASSERT( items2.count(), sizetype(0) ); + + //check macro1 == macro2 + KOMACROTEST_ASSERT( items1.count(), items2.count() ); + + //check the name + KOMACROTEST_ASSERT( QString(macro1->name()), QString("testMacro") ); + + { + const QString tmp1 = QString("test"); + macro1->setName(tmp1); + + //check the name changed + KOMACROTEST_XASSERT( QString(macro1->name()), QString("testMacro") ); + //check the name + KOMACROTEST_ASSERT( QString(macro1->name()), QString("test") ); + } + + //fetch the first one + KSharedPtr<KoMacro::Action> actionptr = items1[0]->action(); + //check that it is not null + KOMACROTEST_XASSERT(sizetype(actionptr.data()), sizetype(0)); + //check that it has the right name + KOMACROTEST_ASSERT( QString(actionptr->name()), QString("testaction") ); + //check that it has the right text + KOMACROTEST_ASSERT( actionptr->text(), QString("Test") ); + + //try to clear items + macro1->clearItems(); + //get items + items1 = macro1->items(); + //check that they are deleted + KOMACROTEST_ASSERT( items1.count(), sizetype(0) ); +} +#include "macrotests.moc" diff --git a/kexi/plugins/macros/tests/macrotests.h b/kexi/plugins/macros/tests/macrotests.h new file mode 100644 index 00000000..ed8d0f21 --- /dev/null +++ b/kexi/plugins/macros/tests/macrotests.h @@ -0,0 +1,74 @@ +/*************************************************************************** + * This file is part of the KDE project + * copyright (C) 2005 by Sebastian Sauer (mail@dipe.org) + * copyright (C) 2005 by Tobi Krebs (tobi.krebs@gmail.com) + * + * 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 KOMACROTEST_MACROTESTS_H +#define KOMACROTEST_MACROTESTS_H + +#include <kunittest/tester.h> + +namespace KoMacroTest { + + /** + * The common testsuite used to test common @a KoMacro + * functionality. + */ + class MacroTests : public KUnitTest::SlotTester + { + Q_OBJECT + public: + + /** + * Constructor. + */ + MacroTests(); + + /** + * Destructor. + */ + virtual ~MacroTests(); + + public slots: + + /** + * This slot got called by KUnitTest before testing + * starts. + */ + void setUp(); + + /** + * This slot got called by KUnitTest after all tests + * are done. + */ + void tearDown(); + + /** + * Test the @a KoMacro::Action functionality. + */ + void testMacro(); + + private: + /// @internal d-pointer class. + class Private; + /// @internal d-pointer instance. + Private* const d; + }; + +} + +#endif diff --git a/kexi/plugins/macros/tests/testaction.cpp b/kexi/plugins/macros/tests/testaction.cpp new file mode 100644 index 00000000..4063aa1b --- /dev/null +++ b/kexi/plugins/macros/tests/testaction.cpp @@ -0,0 +1,61 @@ +/*************************************************************************** + * This file is part of the KDE project + * copyright (C) 2006 by Sebastian Sauer (mail@dipe.org) + * copyright (C) 2006 by Bernd Steindorff (bernd@itii.de) + * copyright (C) 2006 by Sascha Kupper (kusato@kfnv.de) + * + * 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 "testaction.h" + +#include "../lib/action.h" +#include "../lib/context.h" +#include "../lib/macroitem.h" +#include "../lib/variable.h" + +#include <klocale.h> +#include <kdebug.h> + +using namespace KoMacroTest; + +TestAction::TestAction() + : KoMacro::Action("testaction", "Test") +{ + setVariable("teststring", "Stringtest", QString("testString")); + setVariable("testint", "Inttest", int(0)); + setVariable("testdouble", "Doubletest", double(0.5)); + setVariable("testbool", "Booltest", QVariant(true,0)); +} + +TestAction::~TestAction() +{ +} + +bool TestAction::notifyUpdated(KSharedPtr<KoMacro::MacroItem> macroitem, const QString& name) +{ + Q_UNUSED(macroitem); + Q_UNUSED(name); + return true; +} + +void TestAction::activate(KSharedPtr<KoMacro::Context> context) +{ + kdDebug() << "TestAction::activate(KSharedPtr<Context>)" << endl; + const QString teststring = context->variable("teststring")->variant().toString(); + const int testint = context->variable("testint")->variant().toInt(); + const bool testbool = context->variable("testbool")->variant().toBool(); +} + +#include "testaction.moc" diff --git a/kexi/plugins/macros/tests/testaction.h b/kexi/plugins/macros/tests/testaction.h new file mode 100644 index 00000000..9eebff3c --- /dev/null +++ b/kexi/plugins/macros/tests/testaction.h @@ -0,0 +1,78 @@ +/*************************************************************************** + * This file is part of the KDE project + * copyright (C) 2006 by Sebastian Sauer (mail@dipe.org) + * copyright (C) 2006 by Bernd Steindorff (bernd@itii.de) + * copyright (C) 2006 by Sascha Kupper (kusato@kfnv.de) + * + * 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 KOMACROTEST_TESTACTION_H +#define KOMACROTEST_TESTACTION_H + +#include <ksharedptr.h> + +#include "../lib/action.h" + +namespace KoMacro { + class Context; + class MacroItem; +} + +namespace KoMacroTest { + + /** + * This TestAction implements a @a KoMacro::Action to + * test the functionality provided by this class. + */ + class TestAction : public KoMacro::Action + { + Q_OBJECT + public: + + /** + * Constructor. + */ + TestAction(); + + /** + * Destructor. + */ + virtual ~TestAction(); + + /** + * This function is called, when the @a KoMacro::Variable + * with name @p name used within the @a KoMacro::MacroItem + * @p macroitem got changed. + * + * @param macroitem The @a KoMacro::MacroItem instance where + * the variable defined with @p name is located in. + * @param name The name the @a KoMacro::Variable has. + * @return true if the update was successfully else false + * is returned. + */ + virtual bool notifyUpdated(KSharedPtr<KoMacro::MacroItem> macroitem, const QString& name); + + public slots: + + /** + * Called if the @a Action should be executed within the + * defined @p context . + */ + virtual void activate(KSharedPtr<KoMacro::Context> context); + + }; +} + +#endif diff --git a/kexi/plugins/macros/tests/testobject.cpp b/kexi/plugins/macros/tests/testobject.cpp new file mode 100644 index 00000000..39cadb7a --- /dev/null +++ b/kexi/plugins/macros/tests/testobject.cpp @@ -0,0 +1,117 @@ +/*************************************************************************** + * This file is part of the KDE project + * copyright (C) 2005 by Sebastian Sauer (mail@dipe.org) + * copyright (C) 2005 by Tobi Krebs (tobi.krebs@gmail.com) + * + * 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 "testobject.h" + +//#include "../lib/manager.h" +//#include "../lib/action.h" +//#include "../lib/function.h" +//#include "../lib/macro.h" +//#include "../lib/metaobject.h" + +//#include <qstringlist.h> +//#include <qdom.h> + +#include <kdebug.h> +//#include <kxmlguiclient.h> + +using namespace KoMacroTest; + +namespace KoMacroTest { + + /** + * @internal d-pointer class to be more flexible on future extension of the + * functionality without to much risk to break the binary compatibility. + */ + class TestObject::Private + { + public: + + /** + * The @a KUnitTest::Tester instance that likes to test + * our TestObject instance. + */ + KUnitTest::Tester* const tester; + Private(KUnitTest::Tester* const tester) + : tester(tester) + { + } + }; + +} + +TestObject::TestObject(KUnitTest::Tester* const tester) + : QObject() + , d( new Private(tester) ) // create the private d-pointer instance. +{ + setName("TestObject"); +} + +TestObject::~TestObject() +{ + delete d; +} + +//testObject without arguments +void TestObject::myslot() +{ + QString s = "CALLED => TestObject::myslot()"; + //be loud + kdDebug() << s << endl; + //add some extra Debuginfos to TestResults see tester.h + d->tester->results()->addDebugInfo(s); +} + +//testobject with QString and int argument +//int is returnvalue +int TestObject::myslot(const QString&, int i) +{ + QString s = "CALLED => TestObject::myslot(const QString&, int)"; + //be loud + kdDebug() << s << endl; + //add some extra debuginfos to TestResults + d->tester->results()->addDebugInfo(s); + return i; +} + +//testobject with QString argument +//QString is returnvalue +QString TestObject::myslot(const QString& s) +{ + QString t = QString("CALLED => TestObject::myslot(const QString& s) s=%1").arg(s); + //be loud + kdDebug() << t << endl; + //add some extra Debuginfos to TestResults + d->tester->results()->addDebugInfo(t); + return s; +} + +//testobject with QString and double argument +//double is returnvalue +double TestObject::myslot(const QString&, double d) +{ + QString s = "CALLED => TestObject::myslot(const QString&, double)"; + //be loud + kdDebug() << s << endl; + //add some extra Debuginfos to TestResults + this->d->tester->results()->addDebugInfo(s); + return d; +} + +#include "testobject.moc" diff --git a/kexi/plugins/macros/tests/testobject.h b/kexi/plugins/macros/tests/testobject.h new file mode 100644 index 00000000..da5e8ae2 --- /dev/null +++ b/kexi/plugins/macros/tests/testobject.h @@ -0,0 +1,85 @@ +/*************************************************************************** + * This file is part of the KDE project + * copyright (C) 2005 by Sebastian Sauer (mail@dipe.org) + * copyright (C) 2005 by Tobi Krebs (tobi.krebs@gmail.com) + * + * 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 KOMACROTEST_TESTOBJECT_H +#define KOMACROTEST_TESTOBJECT_H + +#include <qobject.h> +#include <kunittest/tester.h> + +namespace KoMacroTest { + + /** + * The TestObject class is used to test handling and communication + * of external from QObject inheritated classes. + */ + class TestObject : public QObject + { + Q_OBJECT + public: + + /** + * Constructor. + * + * @param tester The @a KUnitTest::Tester instance + * that likes to test our TestObject instance. + */ + TestObject(KUnitTest::Tester* const tester); + + /** + * Destructor. + */ + virtual ~TestObject(); + + public slots: + + /** + * This slot got published to the KoMacro-framework + * and will be called to test the functionality. + */ + void myslot(); + + /** + * This slot got published to the KoMacro-framework + * and will be called to test the functionality. + */ + int myslot(const QString&, int); + + /** + * This slot got published to the KoMacro-framework + * and will be called to test the functionality. + */ + QString myslot(const QString&); + + /** + * This slot got published to the KoMacro-framework + * and will be called to test the functionality. + * @return is @param d + */ + double myslot(const QString&, double d); + + private: + /// @internal d-pointer class. + class Private; + /// @internal d-pointer instance. + Private* const d; + }; +} + +#endif diff --git a/kexi/plugins/macros/tests/variabletests.cpp b/kexi/plugins/macros/tests/variabletests.cpp new file mode 100644 index 00000000..8bc8d9c7 --- /dev/null +++ b/kexi/plugins/macros/tests/variabletests.cpp @@ -0,0 +1,236 @@ +/*************************************************************************** + * This file is part of the KDE project + * copyright (C) 2006 by Sebastian Sauer (mail@dipe.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 "variabletests.h" +#include "testobject.h" +#include "testaction.h" +#include "komacrotestbase.h" + +#include "../lib/action.h" +#include "../lib/function.h" +#include "../lib/manager.h" +#include "../lib/macro.h" +#include "../lib/variable.h" +#include "../lib/metaobject.h" +#include "../lib/metamethod.h" +#include "../lib/metaparameter.h" +#include "../lib/exception.h" +#include "../lib/macroitem.h" + +#include <ostream> + +#include <qstringlist.h> +#include <qdom.h> + +#include <kdebug.h> +#include <kunittest/runner.h> +#include <kxmlguiclient.h> + +using namespace KUnitTest; +using namespace KoMacroTest; + +namespace KoMacroTest { + + /** + * Register KoMacroTest::CommonTests as TestSuite. + */ + + KUNITTEST_SUITE("KoMacroTestSuite"); + KUNITTEST_REGISTER_TESTER(VariableTests); + + + class VariableTests::Private + { + public: + /** + * An KXMLGUIClient instance created on @a setUp() and + * passed to the @a KoMacro::Manager to bridge to the + * app-functionality. + */ + KXMLGUIClient* xmlguiclient; + + /** + * An @a TestObject instance used internaly to test + * handling and communication with from QObject + * inheritated instances. + */ + TestAction* testaction; + + QDomDocument* doomdocument; + + KSharedPtr<KoMacro::Macro> macro; + + Private() + : xmlguiclient(0) + , testaction(0) + , doomdocument(0) + , macro(0) + { + } + }; +} + +typedef QValueList< KSharedPtr<KoMacro::MacroItem> >::size_type sizetype; + +/****************************************************************************** +* This is an xtra big TODO: +* - get rid of all double declarations +* - create xtra-class for Variable/Macroitem tests +* - add comments +******************************************************************************/ +VariableTests::VariableTests() + : KUnitTest::SlotTester() + , d( new Private() ) // create the private d-pointer instance. +{ +} + +VariableTests::~VariableTests() +{ + delete d->xmlguiclient; + delete d; +} + + +void VariableTests::setUp() +{ + d->xmlguiclient = new KXMLGUIClient(); + + if (::KoMacro::Manager::self() == 0) { + ::KoMacro::Manager::init( d->xmlguiclient ); + } + + d->testaction = new TestAction(); + ::KoMacro::Manager::self()->publishAction(d->testaction); + + d->doomdocument = new QDomDocument(); + + QString const xml = QString("<!DOCTYPE macros>" + "<macro xmlversion=\"1\" >" + "<item action=\"testaction\" >" + "</item>" + "</macro>"); + + d->doomdocument->setContent(xml); + d->macro = KoMacro::Manager::self()->createMacro("testMacro"); + d->macro->parseXML(d->doomdocument->documentElement()); + d->macro->execute(this); +} + +void VariableTests::tearDown() +{ + delete d->macro; + delete d->doomdocument; + delete d->xmlguiclient; +} + +void VariableTests::testMacro() +{ + kdDebug()<<"===================== testVariable() ===================" << endl; + kdDebug()<<"===================== testMacro() ======================" << endl; + + //fetch Items and .. + QValueList< KSharedPtr<KoMacro::MacroItem> >& items = d->macro->items(); + + //... check that there is one + KOMACROTEST_XASSERT( items.count(), sizetype(0) ); +} + +void VariableTests::testVariableString() { + kdDebug()<<"===================== testVariableString() ======================" << endl; + QValueList< KSharedPtr<KoMacro::MacroItem> >& items = d->macro->items(); + KSharedPtr<KoMacro::Action> actionptr = items[0]->action(); + + //fetch the "teststring"-variable + KSharedPtr<KoMacro::Variable> variableptr = actionptr->variable(TESTSTRING); + //So there is a variable, does hasVariable() work ? + KOMACROTEST_ASSERT(actionptr->hasVariable(TESTSTRING),true); + //check count of variables + KOMACROTEST_ASSERT(sizetype(actionptr->variableNames().count()),sizetype(4)); + //remove one + actionptr->removeVariable(TESTSTRING); + //Decreased ?? + KOMACROTEST_ASSERT(sizetype(actionptr->variableNames().count()),sizetype(3)); + //add one + actionptr->setVariable(variableptr); + //increased ?? + KOMACROTEST_ASSERT(sizetype(actionptr->variableNames().count()),sizetype(4)); + + //check that it is not null + KOMACROTEST_XASSERT(sizetype(variableptr.data()), sizetype(0)); + //check that it is "testString" + KOMACROTEST_ASSERT(variableptr->variant().toString(),QString("testString")); + + actionptr->setVariable("teststring", "STRINGTEST", "TestString"); + variableptr = actionptr->variable("teststring"); + //check that it is not null + KOMACROTEST_XASSERT(sizetype(variableptr.data()), sizetype(0)); + //check that it is " " + KOMACROTEST_ASSERT(variableptr->variant().toString(),QString("TestString")); +} + +void VariableTests::testVariableInt() { + kdDebug()<<"===================== testVariableInt() ======================" << endl; + QValueList< KSharedPtr<KoMacro::MacroItem> >& items = d->macro->items(); + KSharedPtr<KoMacro::Action> actionptr = items[0]->action(); + + //fetch the "testint"-variable + KSharedPtr<KoMacro::Variable> variableptr = actionptr->variable(TESTINT); + //check that it is not null + KOMACROTEST_XASSERT(sizetype(variableptr.data()), sizetype(0)); + //check that it is 0 + KOMACROTEST_ASSERT(variableptr->variant().toInt(),int(0)); + + actionptr->setVariable(TESTINT,"INTTEST",INT_MAX); + variableptr = actionptr->variable("testint"); + KOMACROTEST_XASSERT(sizetype(variableptr.data()), sizetype(0)); + KOMACROTEST_ASSERT(sizetype(variableptr->variant().toInt()),sizetype(INT_MAX)); + + actionptr->setVariable(TESTINT,"INTTEST",INT_MAX+1); + variableptr = actionptr->variable("testint"); + KOMACROTEST_XASSERT(sizetype(variableptr.data()), sizetype(0)); + KOMACROTEST_ASSERT(sizetype(variableptr->variant().toInt()),sizetype(INT_MAX+1)); + + actionptr->setVariable(TESTINT,"INTTEST",INT_MIN); + variableptr = actionptr->variable("testint"); + KOMACROTEST_XASSERT(sizetype(variableptr.data()), sizetype(0)); + KOMACROTEST_ASSERT(sizetype(variableptr->variant().toInt()),sizetype(INT_MIN)); + + actionptr->setVariable(TESTINT,"INTTEST",INT_MIN-1); + variableptr = actionptr->variable("testint"); + KOMACROTEST_XASSERT(sizetype(variableptr.data()), sizetype(0)); + KOMACROTEST_ASSERT(sizetype(variableptr->variant().toInt()),sizetype(INT_MIN-1)); +} + +void VariableTests::testVariableBool() { + kdDebug()<<"===================== testVariableBool() ======================" << endl; + QValueList< KSharedPtr<KoMacro::MacroItem> >& items = d->macro->items(); + KSharedPtr<KoMacro::Action> actionptr = items[0]->action(); + + //fetch the "testbool"-variable + KSharedPtr<KoMacro::Variable> variableptr = actionptr->variable(TESTBOOL); + //check that it is not null + KOMACROTEST_XASSERT(sizetype(variableptr.data()), sizetype(0)); + //check that it is " " + KOMACROTEST_ASSERT(variableptr->variant().toBool(),true); + + actionptr->setVariable("testbool","BOOLTEST", "false"); + variableptr = actionptr->variable("testbool"); + KOMACROTEST_XASSERT(sizetype(variableptr.data()), sizetype(0)); + KOMACROTEST_ASSERT(variableptr->variant().toBool(),false); +} +#include "variabletests.moc" diff --git a/kexi/plugins/macros/tests/variabletests.h b/kexi/plugins/macros/tests/variabletests.h new file mode 100644 index 00000000..5bc7f144 --- /dev/null +++ b/kexi/plugins/macros/tests/variabletests.h @@ -0,0 +1,87 @@ +/*************************************************************************** + * This file is part of the KDE project + * copyright (C) 2005 by Sebastian Sauer (mail@dipe.org) + * copyright (C) 2005 by Tobi Krebs (tobi.krebs@gmail.com) + * + * 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 KOMACROTEST_VARIABLETESTS_H +#define KOMACROTEST_VARIABLETESTS_H + +#include <kunittest/tester.h> + +namespace KoMacroTest { + + /** + * The common testsuite used to test common @a KoMacro + * functionality. + */ + class VariableTests : public KUnitTest::SlotTester + { + Q_OBJECT + public: + + /** + * Constructor. + */ + VariableTests(); + + /** + * Destructor. + */ + virtual ~VariableTests(); + + public slots: + + /** + * This slot got called by KUnitTest before testing + * starts. + */ + void setUp(); + + /** + * This slot got called by KUnitTest after all tests + * are done. + */ + void tearDown(); + + /** + * Subtest for the @a KoMacro::Action functionality. + */ + void testMacro(); + + /** + * Subtest for the @a KoMacro::Action functionality. + */ + void testVariableString(); + /** + * Subtest for the @a KoMacro::Action functionality. + */ + void testVariableInt(); + /** + * Subtest for the @a KoMacro::Action functionality. + */ + void testVariableBool(); + + private: + /// @internal d-pointer class. + class Private; + /// @internal d-pointer instance. + Private* const d; + }; + +} + +#endif diff --git a/kexi/plugins/macros/tests/xmlhandlertests.cpp b/kexi/plugins/macros/tests/xmlhandlertests.cpp new file mode 100644 index 00000000..9a0ebcb1 --- /dev/null +++ b/kexi/plugins/macros/tests/xmlhandlertests.cpp @@ -0,0 +1,619 @@ +/*************************************************************************** + * This file is part of the KDE project + * copyright (C) 2006 by Bernd Steindorff (bernd@itii.de) + * + * 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 "xmlhandlertests.h" +#include "testaction.h" +#include "komacrotestbase.h" + +#include "../lib/action.h" +#include "../lib/manager.h" +#include "../lib/macro.h" +#include "../lib/variable.h" +#include "../lib/macroitem.h" + +#include <ostream> +#include <cfloat> + +#include <qdom.h> + +#include <kdebug.h> +#include <kunittest/runner.h> +#include <kxmlguiclient.h> + +using namespace KUnitTest; +using namespace KoMacroTest; + +namespace KoMacroTest { + + /** + * Register KoMacroTest::CommonTests as TestSuite. + */ + KUNITTEST_SUITE("KoMacroTestSuite") + KUNITTEST_REGISTER_TESTER(XMLHandlerTests); + + class XMLHandlerTests::Private + { + public: + /** + * An KXMLGUIClient instance created on @a setUp() and + * passed to the @a KoMacro::Manager to bridge to the + * app-functionality. + */ + KXMLGUIClient* xmlguiclient; + + /** + * An @a TestObject instance used internaly to test + * handling and communication with from QObject + * inheritated instances. + */ + KSharedPtr<KoMacro::Action> testaction; + + Private() + : xmlguiclient(0) + , testaction(0) + { + } + }; +} + +XMLHandlerTests::XMLHandlerTests() + : KUnitTest::SlotTester() + , d( new Private() ) // create the private d-pointer instance. +{ +} + +XMLHandlerTests::~XMLHandlerTests() +{ + delete d->xmlguiclient; + delete d; +} + + +void XMLHandlerTests::setUp() +{ + d->xmlguiclient = new KXMLGUIClient(); + + //Singelton more or less ... + if (::KoMacro::Manager::self() == 0) { + ::KoMacro::Manager::init( d->xmlguiclient ); + } + + d->testaction = new TestAction(); + ::KoMacro::Manager::self()->publishAction(d->testaction); +} + +void XMLHandlerTests::tearDown() +{ + delete d->xmlguiclient; +} + +/** +* Test the @a KoMacro::XMLHandler parseXML() and toXML()-function. +*/ +void XMLHandlerTests::testParseAndToXML() +{ + kdDebug()<<"===================== testParseAndToXML() ======================" << endl; + + // 1.Test - Correct DomElement. + testCorrectDomElement(); + // 2.Test - XML-document with bad root element. + testBadRoot(); + // 3.Test - XML-document with a missing Variable. + testMissingVariable(); + // 4.Test - One more Variable in XML-Document. + testMoreVariables(); + // 5.Test - XML-document with wrong macro-xmlversion. + testWrongVersion(); + // 6.Test - XML-document if it has a wrong structure like wrong parathesis + // or missing end tag. + testWrongXMLStruct(); + // 7.Test-XML-document with maximum field-size. + testMaxNum(); + // 8.Test-XML-document with maximum+1 field-size. + testMaxNum2(); + // 9.Test-XML-document with minimum field-size. + testMinNum(); + // 10.Test-XML-document with minimum-1 field-size. + testMinNum2(); + // 11.Test - With a to big number. + testBigNumber(); + // 12.Test - With two MacroItems. + testTwoMacroItems(); +} + +/*************************************************************************** +* Begin of Sub-methos of testParseXML(). +***************************************************************************/ +// 1.Test - Correct DomElement. +void XMLHandlerTests::testCorrectDomElement() +{ + // Local Init + KSharedPtr<KoMacro::Macro> macro = KoMacro::Manager::self()->createMacro("testMacro"); + QDomDocument doomdocument; + + // Part 1: From XML to a Macro. + // Test-XML-document with normal allocated variables. + const QString xml = QString("<!DOCTYPE macros>" + "<macro xmlversion=\"1\">" + "<item action=\"testaction\" >" + "<variable name=\"teststring\" >test_string</variable>" + "<variable name=\"testint\" >0</variable>" + "<variable name=\"testbool\" >true</variable>" + "<variable name=\"testdouble\" >0.6</variable>" + "</item>" + "</macro>"); + // Set the XML-document with the above string. + doomdocument.setContent(xml); + const QDomElement elem = doomdocument.documentElement(); + // Is our XML parseable by calling parseXML()? + KOMACROTEST_ASSERT(macro->parseXML(elem),true); + + // Is the parsen content in the Macro correct ? + QMap<QString,bool> isvariableok; + isvariableok["teststring"] = true; + isvariableok["testint"] = true; + isvariableok["testbool"] = true; + isvariableok["testdouble"] = true; + assertMacroContentEqToXML(macro,elem,false,true,isvariableok); + + // Transform back by calling toXML(). + const QDomElement elem2 = macro->toXML(); + assertMacroContentEqToXML(macro,elem2,false,true,isvariableok); + + // Test the Compare-method when a Variable will change, it must fail. + macro->items().first()->variable("teststring")->setVariant("bla"); + isvariableok.replace("teststring",false); + assertMacroContentEqToXML(macro,elem,false,true,isvariableok); +} + +// 2.Test - XML-document with bad root element. +void XMLHandlerTests::testBadRoot() +{ + KSharedPtr<KoMacro::Macro> macro = KoMacro::Manager::self()->createMacro("testMacro"); + QDomDocument doomdocument; + + const QString xml = QString("<!DOCTYPE macros>" + "<maro xmlversion=\"1\">" + "<item action=\"testaction\" >" + "<variable name=\"teststring\" >test_string</variable>" + "<variable name=\"testint\" >0</variable>" + "<variable name=\"testbool\" >true</variable>" + "<variable name=\"testdouble\" >0.6</variable>" + "</item>" + "</maro>"); + doomdocument.setContent(xml); + const QDomElement elem = doomdocument.documentElement(); + KOMACROTEST_XASSERT(macro->parseXML(elem),true); + + //no assertMacroContentEqToXML(), because parsing failed. + assertMacroContentEqToXML(macro,elem,true,false,QMap<QString,bool>()); + + const QDomElement elem2 = macro->toXML(); + assertMacroContentEqToXML(macro,elem2,true,false,QMap<QString,bool>()); +} + +// 3.Test - XML-document with a missing Variable. +void XMLHandlerTests::testMissingVariable() +{ + KSharedPtr<KoMacro::Macro> macro = KoMacro::Manager::self()->createMacro("testMacro"); + QDomDocument doomdocument; + + const QString xml = QString("<!DOCTYPE macros>" + "<macro xmlversion=\"1\">" + "<item action=\"testaction\" >" + "<variable name=\"teststring\" >test_string</variable>" + "<variable name=\"testint\" >0</variable>" + "<variable name=\"testdouble\" >0.6</variable>" + "</item>" + "</macro>"); + doomdocument.setContent(xml); + const QDomElement elem = doomdocument.documentElement(); + KOMACROTEST_ASSERT(macro->parseXML(elem),true); + + QMap<QString,bool> isvariableok; + isvariableok["teststring"] = true; + isvariableok["testint"] = true; + isvariableok["testdouble"] = true; + assertMacroContentEqToXML(macro,elem,false,true,isvariableok); + + const QDomElement elem2 = macro->toXML(); + assertMacroContentEqToXML(macro,elem2,false,true,isvariableok); +} + +// 4.Test - One more Variable in XML-Document. +void XMLHandlerTests::testMoreVariables() +{ + KSharedPtr<KoMacro::Macro> macro = KoMacro::Manager::self()->createMacro("testMacro"); + QDomDocument doomdocument; + + const QString xml = QString("<!DOCTYPE macros>" + "<macro xmlversion=\"1\">" + "<item action=\"testaction\" >" + "<variable name=\"teststring\" >test_string</variable>" + "<variable name=\"testint\" >0</variable>" + "<variable name=\"testbool\" >true</variable>" + "<variable name=\"testdouble\" >0.6</variable>" + "<variable name=\"testbla\" >somethingwrong</variable>" + "</item>" + "</macro>"); + doomdocument.setContent(xml); + const QDomElement elem = doomdocument.documentElement(); + KOMACROTEST_ASSERT(macro->parseXML(elem),true); + + QMap<QString,bool> isvariableok; + isvariableok["teststring"] = true; + isvariableok["testint"] = true; + isvariableok["testbool"] = true; + isvariableok["testdouble"] = true; + isvariableok["testbla"] = true; + assertMacroContentEqToXML(macro,elem,false,true,isvariableok); + + const QDomElement elem2 = macro->toXML(); + assertMacroContentEqToXML(macro,elem2,false,true,isvariableok); +} + +// 5.Test - XML-document with wrong macro-xmlversion. +void XMLHandlerTests::testWrongVersion() +{ + KSharedPtr<KoMacro::Macro> macro = KoMacro::Manager::self()->createMacro("testMacro"); + QDomDocument doomdocument; + + const QString xml = QString("<!DOCTYPE macros>" + "<macro xmlversion=\"2\">" + "<item action=\"testaction\" >" + "<variable name=\"teststring\" >test_string</variable>" + "<variable name=\"testint\" >0</variable>" + "<variable name=\"testbool\" >true</variable>" + "<variable name=\"testdouble\" >0.6</variable>" + "</item>" + "</macro>"); + doomdocument.setContent(xml); + const QDomElement elem = doomdocument.documentElement(); + KOMACROTEST_XASSERT(macro->parseXML(elem),true); + + //no assertMacroContentEqToXML(), because parsing failed. + assertMacroContentEqToXML(macro,elem,true,false,QMap<QString,bool>()); + + const QDomElement elem2 = macro->toXML(); + assertMacroContentEqToXML(macro,elem2,true,false,QMap<QString,bool>()); +} + +// 6.Test - XML-document if it has a wrong structure like wrong parathesis +// or missing end tag. +void XMLHandlerTests::testWrongXMLStruct() +{ + KSharedPtr<KoMacro::Macro> macro = KoMacro::Manager::self()->createMacro("testMacro"); + QDomDocument doomdocument; + + const QString xml = QString("<!DOCTYPE macros>" + "macro xmlversion=\"1\">>" + "<item action=\"testaction\" >" + "<variable name=\"teststring\" >test_string</variable>" + "<variable name=\"testint\" >0</variable>" + "</item>" + "</macro>"); + KOMACROTEST_XASSERT(doomdocument.setContent(xml),true); + const QDomElement elem = doomdocument.documentElement(); + KOMACROTEST_XASSERT(macro->parseXML(elem),true); + + //no assertMacroContentEqToXML(), because parsing failed. + assertMacroContentEqToXML(macro,elem,true,false,QMap<QString,bool>()); + + const QDomElement elem2 = macro->toXML(); + assertMacroContentEqToXML(macro,elem2,true,false,QMap<QString,bool>()); +} + +// 7.Test-XML-document with maximum field-size. +void XMLHandlerTests::testMaxNum() +{ + KSharedPtr<KoMacro::Macro> macro = KoMacro::Manager::self()->createMacro("testMacro"); + QDomDocument doomdocument; + + const QString xml = QString("<!DOCTYPE macros>" + "<macro xmlversion=\"1\">" + "<item action=\"testaction\" >" + "<variable name=\"teststring\" >test_string</variable>" + "<variable name=\"testint\" > %1 </variable>" + "<variable name=\"testbool\" >true</variable>" + "<variable name=\"testdouble\" > %2 </variable>" + "</item>" + "</macro>").arg(INT_MAX).arg(DBL_MAX); + doomdocument.setContent(xml); + const QDomElement elem = doomdocument.documentElement(); + KOMACROTEST_ASSERT(macro->parseXML(elem),true); + + QMap<QString,bool> isvariableok; + isvariableok["teststring"] = true; + isvariableok["testint"] = true; + isvariableok["testbool"] = true; + isvariableok["testdouble"] = true; + assertMacroContentEqToXML(macro,elem,false,true,isvariableok); + + QDomElement elem2 = macro->toXML(); + assertMacroContentEqToXML(macro,elem2,false,true,isvariableok); +} + +// 8.Test-XML-document with maximum+1 field-size. +void XMLHandlerTests::testMaxNum2() +{ + KSharedPtr<KoMacro::Macro> macro = KoMacro::Manager::self()->createMacro("testMacro"); + QDomDocument doomdocument; + + const QString xml = QString("<!DOCTYPE macros>" + "<macro xmlversion=\"1\">" + "<item action=\"testaction\" >" + "<variable name=\"teststring\" >test_string</variable>" + "<variable name=\"testint\" > %1 </variable>" + "<variable name=\"testbool\" >true</variable>" + "<variable name=\"testdouble\" > %2 </variable>" + "</item>" + "</macro>").arg(INT_MAX+1).arg(DBL_MAX+1); + doomdocument.setContent(xml); + const QDomElement elem = doomdocument.documentElement(); + KOMACROTEST_ASSERT(macro->parseXML(elem),true); + + QMap<QString,bool> isvariableok; + isvariableok["teststring"] = true; + isvariableok["testint"] = true; + isvariableok["testbool"] = true; + isvariableok["testdouble"] = true; + assertMacroContentEqToXML(macro,elem,false,true,isvariableok); + + const QDomElement elem2 = macro->toXML(); + assertMacroContentEqToXML(macro,elem2,false,true,isvariableok); +} + +// 9.Test-XML-document with minimum field-size. +void XMLHandlerTests::testMinNum() +{ + KSharedPtr<KoMacro::Macro> macro = KoMacro::Manager::self()->createMacro("testMacro"); + QDomDocument doomdocument; + + const QString xml = QString("<!DOCTYPE macros>" + "<macro xmlversion=\"1\">" + "<item action=\"testaction\" >" + "<variable name=\"teststring\" >test_string</variable>" + "<variable name=\"testint\" > %1 </variable>" + "<variable name=\"testbool\" >true</variable>" + "<variable name=\"testdouble\" > %2 </variable>" + "</item>" + "</macro>").arg(INT_MIN).arg(DBL_MIN); + doomdocument.setContent(xml); + const QDomElement elem = doomdocument.documentElement(); + KOMACROTEST_ASSERT(macro->parseXML(elem),true); + + QMap<QString,bool> isvariableok; + isvariableok["teststring"] = true; + isvariableok["testint"] = true; + isvariableok["testbool"] = true; + isvariableok["testdouble"] = true; + assertMacroContentEqToXML(macro,elem,false,true,isvariableok); + + const QDomElement elem2 = macro->toXML(); + assertMacroContentEqToXML(macro,elem2,false,true,isvariableok); +} + +// 10.Test-XML-document with minimum+1 field-size. +void XMLHandlerTests::testMinNum2() +{ + KSharedPtr<KoMacro::Macro> macro = KoMacro::Manager::self()->createMacro("testMacro"); + QDomDocument doomdocument; + + const QString xml = QString("<!DOCTYPE macros>" + "<macro xmlversion=\"1\">" + "<item action=\"testaction\" >" + "<variable name=\"teststring\" >test_string</variable>" + "<variable name=\"testint\" > %1 </variable>" + "<variable name=\"testbool\" >true</variable>" + "<variable name=\"testdouble\" > %2 </variable>" + "</item>" + "</macro>").arg(INT_MIN-1).arg(DBL_MIN-1); + doomdocument.setContent(xml); + const QDomElement elem = doomdocument.documentElement(); + KOMACROTEST_ASSERT(macro->parseXML(elem),true); + + QMap<QString,bool> isvariableok; + isvariableok["teststring"] = true; + isvariableok["testint"] = true; + isvariableok["testbool"] = true; + isvariableok["testdouble"] = true; + assertMacroContentEqToXML(macro,elem,false,true,isvariableok); + + const QDomElement elem2 = macro->toXML(); + assertMacroContentEqToXML(macro,elem2,false,true,isvariableok); +} + +// 11.Test - With a to big number. +void XMLHandlerTests::testBigNumber() +{ + KSharedPtr<KoMacro::Macro> macro = KoMacro::Manager::self()->createMacro("testMacro"); + QDomDocument doomdocument; + + const QString xml = QString("<!DOCTYPE macros>" + "<macro xmlversion=\"1\">" + "<item action=\"testaction\" >" + "<variable name=\"teststring\" >test_string</variable>" + "<variable name=\"testint\" > 0123456789012345678901234567890123456789 </variable>" + "<variable name=\"testbool\" >true</variable>" + "<variable name=\"testdouble\" > %1 </variable>" + "</item>" + "</macro>").arg(DBL_MAX+1); + doomdocument.setContent(xml); + const QDomElement elem = doomdocument.documentElement(); + KOMACROTEST_ASSERT(macro->parseXML(elem),true); + + QMap<QString,bool> isvariableok; + isvariableok["teststring"] = true; + isvariableok["testint"] = true; + isvariableok["testbool"] = true; + isvariableok["testdouble"] = true; + assertMacroContentEqToXML(macro,elem,false,true,isvariableok); + + const QDomElement elem2 = macro->toXML(); + assertMacroContentEqToXML(macro,elem2,false,true,isvariableok); +} + +// 12.Test - With two MacroItems. +void XMLHandlerTests::testTwoMacroItems() +{ + KSharedPtr<KoMacro::Macro> macro = KoMacro::Manager::self()->createMacro("testMacro"); + QDomDocument doomdocument; + + const QString xml = QString("<!DOCTYPE macros>" + "<macro xmlversion=\"1\">" + "<item action=\"testaction\" >" + "<variable name=\"teststring\" >test_string</variable>" + "<variable name=\"testint\" >0</variable>" + "<variable name=\"testbool\" >true</variable>" + "<variable name=\"testdouble\" >0.6</variable>" + "<variable name=\"testbla\" >somethingwrong</variable>" + "</item>" + "<item action=\"testaction\" >" + "<variable name=\"teststring\" >testBBstring2</variable>" + "<variable name=\"testint\" >4</variable>" + "<variable name=\"testbool\" >false</variable>" + "<variable name=\"testdouble\" >0.7</variable>" + "<variable name=\"testbla\" >somethingwrong2</variable>" + "</item>" + "</macro>"); + doomdocument.setContent(xml); + const QDomElement elem = doomdocument.documentElement(); + KOMACROTEST_ASSERT(macro->parseXML(elem),true); + + QMap<QString,bool> isvariableok; + isvariableok["teststring"] = true; + isvariableok["testint"] = true; + isvariableok["testbool"] = true; + isvariableok["testdouble"] = true; + assertMacroContentEqToXML(macro,elem,false,true,isvariableok); + + const QDomElement elem2 = macro->toXML(); + assertMacroContentEqToXML(macro,elem2,false,true,isvariableok); +} +/*************************************************************************** +* End of Sub-methos of testParseAndToXML(). +***************************************************************************/ + +/** +* Compares a XML-Element with a Macro. Call sub-asserts. +* @p macro The parsen @a Macro. +* @p elem The given @a QDomElement which is parsen. +* @p isitemsempty Bool for expectation of an empty @a MacroItem -List. +* @p isactionset Bool for expectation that the @a Action -names are equal. +* @p isvariableok QMap of Bools for comparing each @a Variable . +*/ +void XMLHandlerTests::assertMacroContentEqToXML(const KSharedPtr<KoMacro::Macro> macro, + const QDomElement& elem, + const bool isitemsempty, + const bool isactionset, + const QMap<QString, bool> isvariableok) +{ + // Make an Iterator over the MacroItems of the Macro. + const QValueList<KSharedPtr<KoMacro::MacroItem > > macroitems = macro->items(); + QValueList<KSharedPtr<KoMacro::MacroItem > >::ConstIterator + mit(macroitems.constBegin()), end(macroitems.constEnd()); + + //1.comparison - Is the MacroItem-list empty? + { + if( isitemsempty ) { + KOMACROTEST_XASSERT(macroitems.empty(),false); + kdDebug() << "There is no correct MacroItem parsen." << endl; + return; + } + else { + KOMACROTEST_ASSERT(macroitems.empty(),false); + } + } + + // Got to the first item-elements of the elem (there is only one in the tests). + QDomNode itemnode = elem.firstChild(); + + // Iterate over the MacroItems and item-elements. + while(mit != end && ! itemnode.isNull()) { + const KSharedPtr<KoMacro::MacroItem> macroitem = *mit; + const QDomElement itemelem = itemnode.toElement(); + + //2.comparison - Is the Action-name equal? + { + if( ! isactionset) { + KOMACROTEST_XASSERT(macroitem->action()->name() == itemelem.attribute("action"),true); + kdDebug() << "Action-name not equal: " + << macroitem->action()->name() + << " != " << itemelem.attribute("action") << endl; + return; + } + else { + KOMACROTEST_ASSERT(macroitem->action()->name() == itemelem.attribute("action"),true); + } + } + + // Go down to MacroItem->Variable and item->variable and compare them. + QMap<QString, KSharedPtr<KoMacro::Variable > > mvariables = macroitem->variables(); + QDomNode varnode = itemelem.firstChild(); + + while ( ! varnode.isNull()) { + const QDomElement varelem = varnode.toElement(); + const KSharedPtr<KoMacro::Variable> varitem = mvariables.find(varelem.attribute("name")).data(); + + //3.comparison - Is the content of the Variable + // in the MacroItem and and item equal? + { + const bool var = *isvariableok.find(varelem.attribute("name")); + if( ! var ) { + KOMACROTEST_XASSERT(varitem->variant() == QVariant(varelem.text()), !var); + kdDebug() << "The content of the Variable: " << varitem->name() + << " is not equal." << varitem->variant() + << "!=" << varelem.text() << endl; + } + else { + KOMACROTEST_ASSERT(varitem->variant() == QVariant(varelem.text()), var); + } + + } + + // Erase the MacroItem from the map, because it is parsen correctly. + mvariables.erase(varitem->name()); + // Go to next Variable in node-tree. + varnode = varnode.nextSibling(); + } + + //4.comparison - Is every MacroItem parsen? + { + KOMACROTEST_ASSERT(mvariables.empty(),true); + kdDebug() << "There are non-filled variable in the MacroItem: " << mvariables.count() <<endl; + } + + // Go to next MacroItem and next item-element. + mit++; + itemnode = itemnode.nextSibling(); + } +} + +// Prints a QMap of Variables to kdDebug(). +void XMLHandlerTests::printMvariables(const QMap<QString, KSharedPtr<KoMacro::Variable > > mvariables, const QString s) +{ + //QValueList<QString>::ConstIterator kit (keys.constBegin()), end(keys.constEnd()); + QMap<QString, KSharedPtr<KoMacro::Variable > >::ConstIterator mvit (mvariables.constBegin()), end(mvariables.constEnd()); + while(mvit != end){ + const KoMacro::Variable * v = *mvit; + kdDebug() << s << ": " << v->name() << endl; + mvit++; + } +} + +#include "xmlhandlertests.moc" diff --git a/kexi/plugins/macros/tests/xmlhandlertests.h b/kexi/plugins/macros/tests/xmlhandlertests.h new file mode 100644 index 00000000..c78a8c79 --- /dev/null +++ b/kexi/plugins/macros/tests/xmlhandlertests.h @@ -0,0 +1,122 @@ +/*************************************************************************** + * This file is part of the KDE project + * copyright (C) 2006 by Bernd Steindorff (bernd@itii.de) + * + * 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 KOMACROTEST_XMLHandlerTests_H +#define KOMACROTEST_XMLHandlerTests_H + +#include <kunittest/tester.h> +#include "../lib/macro.h" + +namespace KoMacroTest { + + /** + * The common testsuite used to test common @a KoMacro + * functionality. + */ + class XMLHandlerTests : public KUnitTest::SlotTester + { + Q_OBJECT + public: + + /** + * Constructor. + */ + XMLHandlerTests(); + + /** + * Destructor. + */ + virtual ~XMLHandlerTests(); + + public slots: + + /** + * This slot got called by KUnitTest before testing + * starts. + */ + void setUp(); + + /** + * This slot got called by KUnitTest after all tests + * are done. + */ + void tearDown(); + + /** + * Test the @a KoMacro::XMLHandler parseXML()-function. + */ + void testParseAndToXML(); + + private: + /// @internal d-pointer class. + class Private; + /// @internal d-pointer instance. + Private* const d; + + /** + * Compares a XML-Element with a Macro. Call sub-asserts. + * @p macro The parsen @a Macro. + * @p domelement The given @a QDomElement which is parsen. + * @p isitemsempty Bool for expectation of an empty @a MacroItem -List. + * @p isactionset Bool for expectation that the @a Action -names are equal. + * @p isvariableok QMap of Bools for comparing each @a Variable . + */ + void assertMacroContentEqToXML(const KSharedPtr<KoMacro::Macro> macro, + const QDomElement& elem, + const bool isitemsempty, + const bool isactionset, + const QMap<QString, bool> isvariableok); + + // Prints a QMap of Variables to kdDebug(). + void printMvariables(const QMap<QString, KSharedPtr<KoMacro::Variable > > mvariables, const QString s); + + /** + * Sub-methods of testParseXML() and testToXML(). + * Test the correct parsing of a @a QDomElement into a @a Macro + * respectively expected failure of parsing. Then transform it + * back and compare it. + */ + // 1.Test - Correct DomElement. + void testCorrectDomElement(); + // 2.Test - XML-document with bad root element. + void testBadRoot(); + // 3.Test - XML-document with a missing Variable. + void testMissingVariable(); + // 4.Test - One more Variable in XML-Document. + void testMoreVariables(); + // 5.Test - XML-document with wrong macro-xmlversion. + void testWrongVersion(); + // 6.Test - XML-document if it has a wrong structure like + // wrong parathesis or missing end tag. + void testWrongXMLStruct(); + // 7.Test-XML-document with maximum field-size. + void testMaxNum(); + // 8.Test-XML-document with maximum+1 field-size. + void testMaxNum2(); + // 9.Test-XML-document with minimum field-size. + void testMinNum(); + // 10.Test-XML-document with minimum-1 field-size. + void testMinNum2(); + // 11.Test - With a to big number. + void testBigNumber(); + // 12.Test - With two MacroItems. + void testTwoMacroItems(); + }; +} + +#endif diff --git a/kexi/plugins/macros/tests/xmlhandlertests2.cpp b/kexi/plugins/macros/tests/xmlhandlertests2.cpp new file mode 100644 index 00000000..2234eaae --- /dev/null +++ b/kexi/plugins/macros/tests/xmlhandlertests2.cpp @@ -0,0 +1,1161 @@ +/*************************************************************************** + * This file is part of the KDE project + * copyright (C) 2006 by Bernd Steindorff (bernd@itii.de) + * + * 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 "xmlhandlertests2.h" +#include "testaction.h" +#include "komacrotestbase.h" + +#include "../lib/action.h" +#include "../lib/manager.h" +#include "../lib/macro.h" +#include "../lib/variable.h" +#include "../lib/macroitem.h" + +#include <ostream> +#include <cfloat> + +#include <qdom.h> + +#include <kdebug.h> +#include <kunittest/runner.h> +#include <kxmlguiclient.h> + +using namespace KUnitTest; +using namespace KoMacroTest; + +namespace KoMacroTest { + + /** + * Register KoMacroTest::CommonTests as TestSuite. + */ + KUNITTEST_SUITE("KoMacroTestSuite") + KUNITTEST_REGISTER_TESTER(XMLHandlerTests2); + + class XMLHandlerTests2::Private + { + public: + /** + * An KXMLGUIClient instance created on @a setUp() and + * passed to the @a KoMacro::Manager to bridge to the + * app-functionality. + */ + KXMLGUIClient* xmlguiclient; + + /** + * @a Macro instance as a container for the macroitems; + */ + KSharedPtr<KoMacro::Macro> macro; // container for manually created items + KSharedPtr<KoMacro::Macro> macro2; // container for parsen items + KSharedPtr<KoMacro::Macro> macro3; // container for parsen items after back-converting by toXML() and again parseXML() + + /** + * An @a TestObject instance used internaly to test + * handling and communication with from QObject + * inheritated instances. + */ + KSharedPtr<KoMacro::Action> testaction; + KSharedPtr<KoMacro::Action> action2; // action of the parsen macro2 + KSharedPtr<KoMacro::Action> action3; // action of the parsen macro3 + KSharedPtr<KoMacro::Action> testaction_2; // for test12 + KSharedPtr<KoMacro::Action> action2_2; // action of the parsen macro2, for test12 + KSharedPtr<KoMacro::Action> action3_2; // action of the parsen macro3, for test12 + + /** + * Represents a @a QValuList of @a MacroItem which are parsen in the + * correspondig @a Macro . + */ + QValueList<KSharedPtr<KoMacro::MacroItem > > macroitems2; // items of macro2 + QValueList<KSharedPtr<KoMacro::MacroItem > > macroitems3; // items of macro3 + + /** + * @a MacroItem instances which ist fillen manually from the given XML + * and parsen by the @a XMLHandler over the XML. + */ + KSharedPtr<KoMacro::MacroItem> macroitem; // created manually from XML + KSharedPtr<KoMacro::MacroItem> macroitem2; // parsen from XML in macro2 + KSharedPtr<KoMacro::MacroItem> macroitem3; // parsen from XML in macro3 + KSharedPtr<KoMacro::MacroItem> macroitem_2; // created manually from XML, for test12 + KSharedPtr<KoMacro::MacroItem> macroitem2_2;// parsen from XML in macro2, for test12 + KSharedPtr<KoMacro::MacroItem> macroitem3_2;// parsen from XML in macro3, for test12 + + Private() + : xmlguiclient(0) + , testaction(0) + { + } + }; +} + +XMLHandlerTests2::XMLHandlerTests2() + : KUnitTest::SlotTester() + , d( new Private() ) // create the private d-pointer instance. +{ +} + +XMLHandlerTests2::~XMLHandlerTests2() +{ + delete d->xmlguiclient; + delete d; +} + + +void XMLHandlerTests2::setUp() +{ + d->xmlguiclient = new KXMLGUIClient(); + + //Singelton more or less ... + if (::KoMacro::Manager::self() == 0) { + ::KoMacro::Manager::init( d->xmlguiclient ); + } + + d->macro = KoMacro::Manager::self()->createMacro("testMacro"); + d->macro2 = KoMacro::Manager::self()->createMacro("testMacro"); + d->macro3 = KoMacro::Manager::self()->createMacro("testMacro"); + + d->testaction = new TestAction(); + d->testaction_2 = new TestAction(); + ::KoMacro::Manager::self()->publishAction(d->testaction); + ::KoMacro::Manager::self()->publishAction(d->testaction_2); +} + +void XMLHandlerTests2::tearDown() +{ + delete d->xmlguiclient; +} + +/** +* Test the @a KoMacro::XMLHandler parseXML() and toXML()-function. +*/ +void XMLHandlerTests2::testParseAndToXML() +{ + kdDebug()<<"===================== testParseAndToXML2() ======================" << endl; + + // 1.Test - Correct DomElement. + testCorrectDomElement(); + // 2.Test - XML-document with bad root element. + testBadRoot(); + // 3.Test - XML-document with a missing Variable. + testMissingVariable(); + // 4.Test - One more Variable in XML-Document. + testMoreVariables(); + // 5.Test - XML-document with wrong macro-xmlversion. + testWrongVersion(); + // 6.Test - XML-document if it has a wrong structure like wrong parathesis + // or missing end tag. + testWrongXMLStruct(); + // 7.Test-XML-document with maximum field-size. + testMaxNum(); + // 8.Test-XML-document with maximum+1 field-size. + testMaxNum2(); + // 9.Test-XML-document with minimum field-size. + testMinNum(); + // 10.Test-XML-document with minimum-1 field-size. + testMinNum2(); + // 11.Test - With a to big number. + testBigNumber(); + // 12.Test - With two MacroItems. + testTwoMacroItems(); +} + + +/*************************************************************************** +* Begin of Sub-methos of testParseXML(). +***************************************************************************/ +// 1.Test - Correct DomElement. +void XMLHandlerTests2::testCorrectDomElement() +{ + // Clear macroitems in the macros. + d->macro->clearItems(); + d->macro2->clearItems(); + d->macro3->clearItems(); + + // Part 1: From XML to a Macro. + // Test-XML-document with normal allocated variables. + const QString xml = QString("<!DOCTYPE macros>" + "<macro xmlversion=\"1\">" + "<item action=\"testaction\" >" + "<variable name=\"teststring\" >test_string</variable>" + "<variable name=\"testint\" >0</variable>" + "<variable name=\"testbool\" >true</variable>" + "<variable name=\"testdouble\" >0.6</variable>" + "</item>" + "</macro>"); + // Set the XML-document with the above string. + QDomDocument doomdocument; + doomdocument.setContent(xml); + const QDomElement elem = doomdocument.documentElement(); + + // Create a MacroItem with the TestAction for macro2 and add it to macro. + d->macroitem = new KoMacro::MacroItem(); + d->macro->addItem(d->macroitem); + + d->macroitem->setAction(d->testaction); + + // Push the Variables into the macroitem. + KSharedPtr<KoMacro::Variable> varstring = d->macroitem->addVariable("teststring",QVariant("test_string")); + KSharedPtr<KoMacro::Variable> varint = d->macroitem->addVariable("testint",QVariant(0)); + KSharedPtr<KoMacro::Variable> varbool = d->macroitem->addVariable("testbool",QVariant(true)); + KSharedPtr<KoMacro::Variable> vardouble = d->macroitem->addVariable("testdouble",QVariant(0.6)); + + // Is our XML parseable into a 2. Macro by calling parseXML()? + KOMACROTEST_ASSERT(d->macro2->parseXML(elem),true); + + // Go down to the MacroItem of macro2. + d->macroitems2 = d->macro2->items(); + // 1a.comparison - Test if the MacroItems have the correct number? + KOMACROTEST_ASSERT(d->macroitems2.size(),(sizetypelist)1); + + { + // 2a.comparison - Test if the Action is correct? + d->macroitem2 = *d->macroitems2.constBegin(); + d->action2 = d->macroitem2->action(); + KOMACROTEST_ASSERT(assertActionsEqual(d->testaction,d->action2),true); + + // 3a.comparison - Test if the Variables have the correct number? + KOMACROTEST_ASSERT(d->macroitem2->variables().size(),(sizetypemap)4); + { + // 4a.comparison - Test if the Variables are equal. + KOMACROTEST_ASSERT(assertVariablesEqual(varstring, d->macroitem2->variable("teststring")), true); + KOMACROTEST_ASSERT(assertVariablesEqual(varint, d->macroitem2->variable("testint")), true); + KOMACROTEST_ASSERT(assertVariablesEqual(varbool, d->macroitem2->variable("testbool")), true); + KOMACROTEST_ASSERT(assertVariablesEqual(vardouble, d->macroitem2->variable("testdouble")), true); + } + } + // Change varint and the belonging Variable in the parsen macro2test + // and test it in the macro3 below + varint->setVariant(117); + d->macroitem2->variable("testint")->setVariant(117); + + // Now convert the parsen macro2 back to a QDomElement and again into macro3 for a better comparison. + const QDomElement elem2 = d->macro2->toXML(); + KOMACROTEST_ASSERT(d->macro3->parseXML(elem2),true); + + // Go down to the MacroItem of macro2. + d->macroitems3 = d->macro3->items(); + // 1b.comparison - Test if the MacroItems have the correct number? + KOMACROTEST_ASSERT(d->macroitems3.size(),(sizetypelist)1); + + { + // 2b.comparison - Test if the Action is correct? + d->macroitem3 = *d->macroitems3.constBegin(); + d->action3 = d->macroitem3->action(); + KOMACROTEST_ASSERT(assertActionsEqual(d->testaction,d->action3),true); + + // 3b.comparison - Test if the Variables have the correct number? + KOMACROTEST_ASSERT(d->macroitem3->variables().size(),(sizetypemap)4); + { + // 4b.comparison - Test if the Variables are equal. + KOMACROTEST_ASSERT(assertVariablesEqual(varstring, d->macroitem3->variable("teststring")), true); + KOMACROTEST_ASSERT(assertVariablesEqual(varint, d->macroitem3->variable("testint")), true); + KOMACROTEST_ASSERT(assertVariablesEqual(varbool, d->macroitem3->variable("testbool")), true); + KOMACROTEST_ASSERT(assertVariablesEqual(vardouble, d->macroitem3->variable("testdouble")), true); + } + } +} + + +// 2.Test - XML-document with bad root element. +void XMLHandlerTests2::testBadRoot() +{ + // Clear macroitems in the macros. + d->macro->clearItems(); + d->macro2->clearItems(); + d->macro3->clearItems(); + + // Part 1: From XML to a Macro. + // Test-XML-document with normal allocated variables. + const QString xml = QString("<!DOCTYPE macros>" + "<maro xmlversion=\"1\">" + "<item action=\"testaction\" >" + "<variable name=\"teststring\" >test_string</variable>" + "<variable name=\"testint\" >0</variable>" + "<variable name=\"testbool\" >true</variable>" + "<variable name=\"testdouble\" >0.6</variable>" + "</item>" + "</maro>"); + // Set the XML-document with the above string. + QDomDocument doomdocument; + doomdocument.setContent(xml); + const QDomElement elem = doomdocument.documentElement(); + + // Create a MacroItem with the TestAction for macro2 and add it to macro. + d->macroitem = new KoMacro::MacroItem(); + d->macro->addItem(d->macroitem); + + d->macroitem->setAction(d->testaction); + + // Push the Variables into the macroitem. + KSharedPtr<KoMacro::Variable> varstring = d->macroitem->addVariable("teststring",QVariant("test_string")); + KSharedPtr<KoMacro::Variable> varint = d->macroitem->addVariable("testint",QVariant(0)); + KSharedPtr<KoMacro::Variable> varbool = d->macroitem->addVariable("testbool",QVariant(true)); + KSharedPtr<KoMacro::Variable> vardouble = d->macroitem->addVariable("testdouble",QVariant(0.6)); + + // Is our XML parseable into a 2. Macro by calling parseXML()? + KOMACROTEST_XASSERT(d->macro2->parseXML(elem),true); +} + +// 3.Test - XML-document with a missing Variable. +void XMLHandlerTests2::testMissingVariable() +{ + // Clear macroitems in the macros. + d->macro->clearItems(); + d->macro2->clearItems(); + d->macro3->clearItems(); + + // Part 1: From XML to a Macro. + // Test-XML-document with normal allocated variables. + const QString xml = QString("<!DOCTYPE macros>" + "<macro xmlversion=\"1\">" + "<item action=\"testaction\" >" + "<variable name=\"teststring\" >test_string</variable>" + "<variable name=\"testint\" >0</variable>" + "<variable name=\"testdouble\" >0.6</variable>" + "</item>" + "</macro>"); + // Set the XML-document with the above string. + QDomDocument doomdocument; + doomdocument.setContent(xml); + const QDomElement elem = doomdocument.documentElement(); + + // Create a MacroItem with the TestAction for macro2 and add it to macro. + d->macroitem = new KoMacro::MacroItem(); + d->macro->addItem(d->macroitem); + + d->macroitem->setAction(d->testaction); + + // Push the Variables into the macroitem. + KSharedPtr<KoMacro::Variable> varstring = d->macroitem->addVariable("teststring",QVariant("test_string")); + KSharedPtr<KoMacro::Variable> varint = d->macroitem->addVariable("testint",QVariant(0)); + KSharedPtr<KoMacro::Variable> vardouble = d->macroitem->addVariable("testdouble",QVariant(0.6)); + + // Is our XML parseable into a 2. Macro by calling parseXML()? + KOMACROTEST_ASSERT(d->macro2->parseXML(elem),true); + + // Go down to the MacroItem of macro2. + d->macroitems2 = d->macro2->items(); + // 1a.comparison - Test if the MacroItems have the correct number? + KOMACROTEST_ASSERT(d->macroitems2.size(),(sizetypelist)1); + + { + // 2a.comparison - Test if the Action is correct? + d->macroitem2 = *d->macroitems2.constBegin(); + d->action2 = d->macroitem2->action(); + KOMACROTEST_ASSERT(assertActionsEqual(d->testaction,d->action2),true); + + // 3a.comparison - Test if the Variables have the correct number? + KOMACROTEST_ASSERT(d->macroitem2->variables().size(),(sizetypemap)3); + { + // 4a.comparison - Test if the Variables are equal. + KOMACROTEST_ASSERT(assertVariablesEqual(varstring, d->macroitem2->variable("teststring")), true); + KOMACROTEST_ASSERT(assertVariablesEqual(varint, d->macroitem2->variable("testint")), true); + KOMACROTEST_ASSERT(assertVariablesEqual(vardouble, d->macroitem2->variable("testdouble")), true); + } + } + + // Now convert the parsen macro2 back to a QDomElement and again into macro3 for a better comparison. + const QDomElement elem2 = d->macro2->toXML(); + KOMACROTEST_ASSERT(d->macro3->parseXML(elem2),true); + + // Go down to the MacroItem of macro2. + d->macroitems3 = d->macro3->items(); + // 1b.comparison - Test if the MacroItems have the correct number? + KOMACROTEST_ASSERT(d->macroitems3.size(),(sizetypelist)1); + + { + // 2b.comparison - Test if the Action is correct? + d->macroitem3 = *d->macroitems3.constBegin(); + d->action3 = d->macroitem3->action(); + KOMACROTEST_ASSERT(assertActionsEqual(d->testaction,d->action3),true); + + // 3b.comparison - Test if the Variables have the correct number? + KOMACROTEST_ASSERT(d->macroitem3->variables().size(),(sizetypemap)3); + { + // 4b.comparison - Test if the Variables are equal. + KOMACROTEST_ASSERT(assertVariablesEqual(varstring, d->macroitem3->variable("teststring")), true); + KOMACROTEST_ASSERT(assertVariablesEqual(varint, d->macroitem3->variable("testint")), true); + KOMACROTEST_ASSERT(assertVariablesEqual(vardouble, d->macroitem3->variable("testdouble")), true); + } + } +} + +// 4.Test - One more Variable in XML-Document. +void XMLHandlerTests2::testMoreVariables() +{ + // Clear macroitems in the macros. + d->macro->clearItems(); + d->macro2->clearItems(); + d->macro3->clearItems(); + + // Part 1: From XML to a Macro. + // Test-XML-document with normal allocated variables. + const QString xml = QString("<!DOCTYPE macros>" + "<macro xmlversion=\"1\">" + "<item action=\"testaction\" >" + "<variable name=\"teststring\" >test_string</variable>" + "<variable name=\"testint\" >0</variable>" + "<variable name=\"testbool\" >true</variable>" + "<variable name=\"testdouble\" >0.6</variable>" + "<variable name=\"testbla\" >somethingwrong</variable>" + "</item>" + "</macro>"); + // Set the XML-document with the above string. + QDomDocument doomdocument; + doomdocument.setContent(xml); + const QDomElement elem = doomdocument.documentElement(); + + // Create a MacroItem with the TestAction for macro2 and add it to macro. + d->macroitem = new KoMacro::MacroItem(); + d->macro->addItem(d->macroitem); + + d->macroitem->setAction(d->testaction); + + // Push the Variables into the macroitem. + KSharedPtr<KoMacro::Variable> varstring = d->macroitem->addVariable("teststring",QVariant("test_string")); + KSharedPtr<KoMacro::Variable> varint = d->macroitem->addVariable("testint",QVariant(0)); + KSharedPtr<KoMacro::Variable> varbool = d->macroitem->addVariable("testbool",QVariant(true)); + KSharedPtr<KoMacro::Variable> vardouble = d->macroitem->addVariable("testdouble",QVariant(0.6)); + KSharedPtr<KoMacro::Variable> varbla = d->macroitem->addVariable("testbla","somethingwrong"); + + // Is our XML parseable into a 2. Macro by calling parseXML()? + KOMACROTEST_ASSERT(d->macro2->parseXML(elem),true); + + // Go down to the MacroItem of macro2. + d->macroitems2 = d->macro2->items(); + // 1a.comparison - Test if the MacroItems have the correct number? + KOMACROTEST_ASSERT(d->macroitems2.size(),(sizetypelist)1); + + { + // 2a.comparison - Test if the Action is correct? + d->macroitem2 = *d->macroitems2.constBegin(); + d->action2 = d->macroitem2->action(); + KOMACROTEST_ASSERT(assertActionsEqual(d->testaction,d->action2),true); + + // 3a.comparison - Test if the Variables have the correct number? + KOMACROTEST_ASSERT(d->macroitem2->variables().size(),(sizetypemap)5); + { + // 4a.comparison - Test if the Variables are equal. + KOMACROTEST_ASSERT(assertVariablesEqual(varstring, d->macroitem2->variable("teststring")), true); + KOMACROTEST_ASSERT(assertVariablesEqual(varint, d->macroitem2->variable("testint")), true); + KOMACROTEST_ASSERT(assertVariablesEqual(varbool, d->macroitem2->variable("testbool")), true); + KOMACROTEST_ASSERT(assertVariablesEqual(vardouble, d->macroitem2->variable("testdouble")), true); + KOMACROTEST_ASSERT(assertVariablesEqual(varbla, d->macroitem2->variable("testbla")), true); + } + } + + // Now convert the parsen macro2 back to a QDomElement and again into macro3 for a better comparison. + const QDomElement elem2 = d->macro2->toXML(); + KOMACROTEST_ASSERT(d->macro3->parseXML(elem2),true); + + // Go down to the MacroItem of macro2. + d->macroitems3 = d->macro3->items(); + // 1b.comparison - Test if the MacroItems have the correct number? + KOMACROTEST_ASSERT(d->macroitems3.size(),(sizetypelist)1); + + { + // 2b.comparison - Test if the Action is correct? + d->macroitem3 = *d->macroitems3.constBegin(); + d->action3 = d->macroitem3->action(); + KOMACROTEST_ASSERT(assertActionsEqual(d->testaction,d->action3),true); + + // 3b.comparison - Test if the Variables have the correct number? + KOMACROTEST_ASSERT(d->macroitem3->variables().size(),(sizetypemap)5); + { + // 4b.comparison - Test if the Variables are equal. + KOMACROTEST_ASSERT(assertVariablesEqual(varstring, d->macroitem3->variable("teststring")), true); + KOMACROTEST_ASSERT(assertVariablesEqual(varint, d->macroitem3->variable("testint")), true); + KOMACROTEST_ASSERT(assertVariablesEqual(varbool, d->macroitem3->variable("testbool")), true); + KOMACROTEST_ASSERT(assertVariablesEqual(vardouble, d->macroitem3->variable("testdouble")), true); + KOMACROTEST_ASSERT(assertVariablesEqual(varbla, d->macroitem3->variable("testbla")), true); + } + } +} + + +// 5.Test - XML-document with wrong macro-xmlversion. +void XMLHandlerTests2::testWrongVersion() +{ + // Clear macroitems in the macros. + d->macro->clearItems(); + d->macro2->clearItems(); + d->macro3->clearItems(); + + // Part 1: From XML to a Macro. + // Test-XML-document with normal allocated variables. + const QString xml = QString("<!DOCTYPE macros>" + "<maro xmlversion=\"2\">" + "<item action=\"testaction\" >" + "<variable name=\"teststring\" >test_string</variable>" + "<variable name=\"testint\" >0</variable>" + "<variable name=\"testbool\" >true</variable>" + "<variable name=\"testdouble\" >0.6</variable>" + "</item>" + "</maro>"); + // Set the XML-document with the above string. + QDomDocument doomdocument; + doomdocument.setContent(xml); + const QDomElement elem = doomdocument.documentElement(); + + // Create a MacroItem with the TestAction for macro2 and add it to macro. + d->macroitem = new KoMacro::MacroItem(); + d->macro->addItem(d->macroitem); + + d->macroitem->setAction(d->testaction); + + // Push the Variables into the macroitem. + KSharedPtr<KoMacro::Variable> varstring = d->macroitem->addVariable("teststring",QVariant("test_string")); + KSharedPtr<KoMacro::Variable> varint = d->macroitem->addVariable("testint",QVariant(0)); + KSharedPtr<KoMacro::Variable> varbool = d->macroitem->addVariable("testbool",QVariant(true)); + KSharedPtr<KoMacro::Variable> vardouble = d->macroitem->addVariable("testdouble",QVariant(0.6)); + + // Is our XML parseable into a 2. Macro by calling parseXML()? + KOMACROTEST_XASSERT(d->macro2->parseXML(elem),true); +} + + +// 6.Test - XML-document if it has a wrong structure like wrong parathesis +// or missing end tag. +void XMLHandlerTests2::testWrongXMLStruct() +{ + // Clear macroitems in the macros. + d->macro->clearItems(); + d->macro2->clearItems(); + d->macro3->clearItems(); + + // Part 1: From XML to a Macro. + // Test-XML-document with normal allocated variables. + const QString xml = QString("<!DOCTYPE macros>" + "maro xmlversion=\"1\">" + "<item action=\"testaction\" >" + "<variable name=\"teststring\" >test_string</variable>" + "<variable name=\"testint\" >0</variable>" + "<variable name=\"testbool\" >true</variable>" + "<variable name=\"testdouble\" >0.6</variable>" + "</item>" + "</maro>"); + // Set the XML-document with the above string. + QDomDocument doomdocument; + doomdocument.setContent(xml); + const QDomElement elem = doomdocument.documentElement(); + + // Create a MacroItem with the TestAction for macro2 and add it to macro. + d->macroitem = new KoMacro::MacroItem(); + d->macro->addItem(d->macroitem); + + d->macroitem->setAction(d->testaction); + + // Push the Variables into the macroitem. + KSharedPtr<KoMacro::Variable> varstring = d->macroitem->addVariable("teststring",QVariant("test_string")); + KSharedPtr<KoMacro::Variable> varint = d->macroitem->addVariable("testint",QVariant(0)); + KSharedPtr<KoMacro::Variable> varbool = d->macroitem->addVariable("testbool",QVariant(true)); + KSharedPtr<KoMacro::Variable> vardouble = d->macroitem->addVariable("testdouble",QVariant(0.6)); + + // Is our XML parseable into a 2. Macro by calling parseXML()? + KOMACROTEST_XASSERT(d->macro2->parseXML(elem),true); +} + +// 7.Test-XML-document with maximum field-size. +void XMLHandlerTests2::testMaxNum() +{ + // Clear macroitems in the macros. + d->macro->clearItems(); + d->macro2->clearItems(); + d->macro3->clearItems(); + + // Part 1: From XML to a Macro. + // Test-XML-document with normal allocated variables. + const QString xml = QString("<!DOCTYPE macros>" + "<macro xmlversion=\"1\">" + "<item action=\"testaction\" >" + "<variable name=\"teststring\" >test_string</variable>" + "<variable name=\"testint\" > %1 </variable>" + "<variable name=\"testbool\" >true</variable>" + "<variable name=\"testdouble\" > %2 </variable>" + "</item>" + "</macro>").arg(INT_MAX).arg(DBL_MAX); + // Set the XML-document with the above string. + QDomDocument doomdocument; + doomdocument.setContent(xml); + const QDomElement elem = doomdocument.documentElement(); + + // Create a MacroItem with the TestAction for macro2 and add it to macro. + d->macroitem = new KoMacro::MacroItem(); + d->macro->addItem(d->macroitem); + + d->macroitem->setAction(d->testaction); + + // Push the Variables into the macroitem. + KSharedPtr<KoMacro::Variable> varstring = d->macroitem->addVariable("teststring",QVariant("test_string")); + KSharedPtr<KoMacro::Variable> varint = d->macroitem->addVariable("testint",QVariant(INT_MAX)); + KSharedPtr<KoMacro::Variable> varbool = d->macroitem->addVariable("testbool",QVariant(true)); + KSharedPtr<KoMacro::Variable> vardouble = d->macroitem->addVariable("testdouble",QVariant(DBL_MAX)); + + // Is our XML parseable into a 2. Macro by calling parseXML()? + KOMACROTEST_ASSERT(d->macro2->parseXML(elem),true); + + // Go down to the MacroItem of macro2. + d->macroitems2 = d->macro2->items(); + // 1a.comparison - Test if the MacroItems have the correct number? + KOMACROTEST_ASSERT(d->macroitems2.size(),(sizetypelist)1); + + { + // 2a.comparison - Test if the Action is correct? + d->macroitem2 = *d->macroitems2.constBegin(); + d->action2 = d->macroitem2->action(); + KOMACROTEST_ASSERT(assertActionsEqual(d->testaction,d->action2),true); + + // 3a.comparison - Test if the Variables have the correct number? + KOMACROTEST_ASSERT(d->macroitem2->variables().size(),(sizetypemap)4); + { + // 4a.comparison - Test if the Variables are equal. + KOMACROTEST_ASSERT(assertVariablesEqual(varstring, d->macroitem2->variable("teststring")), true); + KOMACROTEST_ASSERT(assertVariablesEqual(varint, d->macroitem2->variable("testint")), true); + KOMACROTEST_ASSERT(assertVariablesEqual(varbool, d->macroitem2->variable("testbool")), true); +// KOMACROTEST_ASSERT(assertVariablesEqual(vardouble, d->macroitem2->variable("testdouble")), true); + } + } + + // Now convert the parsen macro2 back to a QDomElement and again into macro3 for a better comparison. + const QDomElement elem2 = d->macro2->toXML(); + KOMACROTEST_ASSERT(d->macro3->parseXML(elem2),true); + + // Go down to the MacroItem of macro2. + d->macroitems3 = d->macro3->items(); + // 1b.comparison - Test if the MacroItems have the correct number? + KOMACROTEST_ASSERT(d->macroitems3.size(),(sizetypelist)1); + + { + // 2b.comparison - Test if the Action is correct? + d->macroitem3 = *d->macroitems3.constBegin(); + d->action3 = d->macroitem3->action(); + KOMACROTEST_ASSERT(assertActionsEqual(d->testaction,d->action3),true); + + // 3b.comparison - Test if the Variables have the correct number? + KOMACROTEST_ASSERT(d->macroitem3->variables().size(),(sizetypemap)4); + { + // 4b.comparison - Test if the Variables are equal. + KOMACROTEST_ASSERT(assertVariablesEqual(varstring, d->macroitem3->variable("teststring")), true); + KOMACROTEST_ASSERT(assertVariablesEqual(varint, d->macroitem3->variable("testint")), true); + KOMACROTEST_ASSERT(assertVariablesEqual(varbool, d->macroitem3->variable("testbool")), true); +// KOMACROTEST_ASSERT(assertVariablesEqual(vardouble, d->macroitem3->variable("testdouble")),true); + } + } +} + +// 8.Test-XML-document with maximum+1 field-size. +void XMLHandlerTests2::testMaxNum2() +{ + // Clear macroitems in the macros. + d->macro->clearItems(); + d->macro2->clearItems(); + d->macro3->clearItems(); + + // Part 1: From XML to a Macro. + // Test-XML-document with normal allocated variables. + const QString xml = QString("<!DOCTYPE macros>" + "<macro xmlversion=\"1\">" + "<item action=\"testaction\" >" + "<variable name=\"teststring\" >test_string</variable>" + "<variable name=\"testint\" > %1 </variable>" + "<variable name=\"testbool\" >true</variable>" + "<variable name=\"testdouble\" > %2 </variable>" + "</item>" + "</macro>").arg(INT_MAX+1).arg(DBL_MAX+1); + // Set the XML-document with the above string. + QDomDocument doomdocument; + doomdocument.setContent(xml); + const QDomElement elem = doomdocument.documentElement(); + + // Create a MacroItem with the TestAction for macro2 and add it to macro. + d->macroitem = new KoMacro::MacroItem(); + d->macro->addItem(d->macroitem); + + d->macroitem->setAction(d->testaction); + + // Push the Variables into the macroitem. + KSharedPtr<KoMacro::Variable> varstring = d->macroitem->addVariable("teststring",QVariant("test_string")); + KSharedPtr<KoMacro::Variable> varint = d->macroitem->addVariable("testint",QVariant(INT_MAX+1)); + KSharedPtr<KoMacro::Variable> varbool = d->macroitem->addVariable("testbool",QVariant(true)); + KSharedPtr<KoMacro::Variable> vardouble = d->macroitem->addVariable("testdouble",QVariant(DBL_MAX+1)); + + // Is our XML parseable into a 2. Macro by calling parseXML()? + KOMACROTEST_ASSERT(d->macro2->parseXML(elem),true); + + // Go down to the MacroItem of macro2. + d->macroitems2 = d->macro2->items(); + // 1a.comparison - Test if the MacroItems have the correct number? + KOMACROTEST_ASSERT(d->macroitems2.size(),(sizetypelist)1); + + { + // 2a.comparison - Test if the Action is correct? + d->macroitem2 = *d->macroitems2.constBegin(); + d->action2 = d->macroitem2->action(); + KOMACROTEST_ASSERT(assertActionsEqual(d->testaction,d->action2),true); + + // 3a.comparison - Test if the Variables have the correct number? + KOMACROTEST_ASSERT(d->macroitem2->variables().size(),(sizetypemap)4); + { + // 4a.comparison - Test if the Variables are equal. + KOMACROTEST_ASSERT(assertVariablesEqual(varstring, d->macroitem2->variable("teststring")), true); + KOMACROTEST_ASSERT(assertVariablesEqual(varint, d->macroitem2->variable("testint")), true); + KOMACROTEST_ASSERT(assertVariablesEqual(varbool, d->macroitem2->variable("testbool")), true); +// KOMACROTEST_ASSERT(assertVariablesEqual(vardouble, d->macroitem2->variable("testdouble")),true); + } + } + + // Now convert the parsen macro2 back to a QDomElement and again into macro3 for a better comparison. + const QDomElement elem2 = d->macro2->toXML(); + KOMACROTEST_ASSERT(d->macro3->parseXML(elem2),true); + + // Go down to the MacroItem of macro2. + d->macroitems3 = d->macro3->items(); + // 1b.comparison - Test if the MacroItems have the correct number? + KOMACROTEST_ASSERT(d->macroitems3.size(),(sizetypelist)1); + + { + // 2b.comparison - Test if the Action is correct? + d->macroitem3 = *d->macroitems3.constBegin(); + d->action3 = d->macroitem3->action(); + KOMACROTEST_ASSERT(assertActionsEqual(d->testaction,d->action3),true); + + // 3b.comparison - Test if the Variables have the correct number? + KOMACROTEST_ASSERT(d->macroitem3->variables().size(),(sizetypemap)4); + { + // 4b.comparison - Test if the Variables are equal. + KOMACROTEST_ASSERT(assertVariablesEqual(varstring, d->macroitem3->variable("teststring")), true); + KOMACROTEST_ASSERT(assertVariablesEqual(varint, d->macroitem3->variable("testint")), true); + KOMACROTEST_ASSERT(assertVariablesEqual(varbool, d->macroitem3->variable("testbool")), true); +// KOMACROTEST_ASSERT(assertVariablesEqual(vardouble, d->macroitem3->variable("testdouble")),true); + } + } +} + +// 9.Test-XML-document with minimum field-size. +void XMLHandlerTests2::testMinNum() +{ + // Clear macroitems in the macros. + d->macro->clearItems(); + d->macro2->clearItems(); + d->macro3->clearItems(); + + // Part 1: From XML to a Macro. + // Test-XML-document with normal allocated variables. + const QString xml = QString("<!DOCTYPE macros>" + "<macro xmlversion=\"1\">" + "<item action=\"testaction\" >" + "<variable name=\"teststring\" >test_string</variable>" + "<variable name=\"testint\" > %1 </variable>" + "<variable name=\"testbool\" >true</variable>" + "<variable name=\"testdouble\" > %2 </variable>" + "</item>" + "</macro>").arg(INT_MIN).arg(DBL_MIN); + // Set the XML-document with the above string. + QDomDocument doomdocument; + doomdocument.setContent(xml); + const QDomElement elem = doomdocument.documentElement(); + + // Create a MacroItem with the TestAction for macro2 and add it to macro. + d->macroitem = new KoMacro::MacroItem(); + d->macro->addItem(d->macroitem); + + d->macroitem->setAction(d->testaction); + + // Push the Variables into the macroitem. + KSharedPtr<KoMacro::Variable> varstring = d->macroitem->addVariable("teststring",QVariant("test_string")); + KSharedPtr<KoMacro::Variable> varint = d->macroitem->addVariable("testint",QVariant(INT_MIN)); + KSharedPtr<KoMacro::Variable> varbool = d->macroitem->addVariable("testbool",QVariant(true)); + KSharedPtr<KoMacro::Variable> vardouble = d->macroitem->addVariable("testdouble",QVariant(DBL_MIN)); + + // Is our XML parseable into a 2. Macro by calling parseXML()? + KOMACROTEST_ASSERT(d->macro2->parseXML(elem),true); + + // Go down to the MacroItem of macro2. + d->macroitems2 = d->macro2->items(); + // 1a.comparison - Test if the MacroItems have the correct number? + KOMACROTEST_ASSERT(d->macroitems2.size(),(sizetypelist)1); + + { + // 2a.comparison - Test if the Action is correct? + d->macroitem2 = *d->macroitems2.constBegin(); + d->action2 = d->macroitem2->action(); + KOMACROTEST_ASSERT(assertActionsEqual(d->testaction,d->action2),true); + + // 3a.comparison - Test if the Variables have the correct number? + KOMACROTEST_ASSERT(d->macroitem2->variables().size(),(sizetypemap)4); + { + // 4a.comparison - Test if the Variables are equal. + KOMACROTEST_ASSERT(assertVariablesEqual(varstring, d->macroitem2->variable("teststring")), true); + KOMACROTEST_ASSERT(assertVariablesEqual(varint, d->macroitem2->variable("testint")), true); + KOMACROTEST_ASSERT(assertVariablesEqual(varbool, d->macroitem2->variable("testbool")), true); +// KOMACROTEST_ASSERT(assertVariablesEqual(vardouble, d->macroitem2->variable("testdouble")),true); + } + } + + // Now convert the parsen macro2 back to a QDomElement and again into macro3 for a better comparison. + const QDomElement elem2 = d->macro2->toXML(); + KOMACROTEST_ASSERT(d->macro3->parseXML(elem2),true); + + // Go down to the MacroItem of macro2. + d->macroitems3 = d->macro3->items(); + // 1b.comparison - Test if the MacroItems have the correct number? + KOMACROTEST_ASSERT(d->macroitems3.size(),(sizetypelist)1); + + { + // 2b.comparison - Test if the Action is correct? + d->macroitem3 = *d->macroitems3.constBegin(); + d->action3 = d->macroitem3->action(); + KOMACROTEST_ASSERT(assertActionsEqual(d->testaction,d->action3),true); + + // 3b.comparison - Test if the Variables have the correct number? + KOMACROTEST_ASSERT(d->macroitem3->variables().size(),(sizetypemap)4); + { + // 4b.comparison - Test if the Variables are equal. + KOMACROTEST_ASSERT(assertVariablesEqual(varstring, d->macroitem3->variable("teststring")), true); + KOMACROTEST_ASSERT(assertVariablesEqual(varint, d->macroitem3->variable("testint")), true); + KOMACROTEST_ASSERT(assertVariablesEqual(varbool, d->macroitem3->variable("testbool")), true); +// KOMACROTEST_ASSERT(assertVariablesEqual(vardouble, d->macroitem3->variable("testdouble")),true); + } + } +} + +// 10.Test-XML-document with minimum+1 field-size. +void XMLHandlerTests2::testMinNum2() +{ + // Clear macroitems in the macros. + d->macro->clearItems(); + d->macro2->clearItems(); + d->macro3->clearItems(); + + // Part 1: From XML to a Macro. + // Test-XML-document with normal allocated variables. + const QString xml = QString("<!DOCTYPE macros>" + "<macro xmlversion=\"1\">" + "<item action=\"testaction\" >" + "<variable name=\"teststring\" >test_string</variable>" + "<variable name=\"testint\" > %1 </variable>" + "<variable name=\"testbool\" >true</variable>" + "<variable name=\"testdouble\" > %2 </variable>" + "</item>" + "</macro>").arg(INT_MIN-1).arg(DBL_MIN-1); + // Set the XML-document with the above string. + QDomDocument doomdocument; + doomdocument.setContent(xml); + const QDomElement elem = doomdocument.documentElement(); + + // Create a MacroItem with the TestAction for macro2 and add it to macro. + d->macroitem = new KoMacro::MacroItem(); + d->macro->addItem(d->macroitem); + + d->macroitem->setAction(d->testaction); + + // Push the Variables into the macroitem. + KSharedPtr<KoMacro::Variable> varstring = d->macroitem->addVariable("teststring",QVariant("test_string")); + KSharedPtr<KoMacro::Variable> varint = d->macroitem->addVariable("testint",QVariant(INT_MIN-1)); + KSharedPtr<KoMacro::Variable> varbool = d->macroitem->addVariable("testbool",QVariant(true)); + KSharedPtr<KoMacro::Variable> vardouble = d->macroitem->addVariable("testdouble",QVariant(DBL_MIN-1)); + + // Is our XML parseable into a 2. Macro by calling parseXML()? + KOMACROTEST_ASSERT(d->macro2->parseXML(elem),true); + + // Go down to the MacroItem of macro2. + d->macroitems2 = d->macro2->items(); + // 1a.comparison - Test if the MacroItems have the correct number? + KOMACROTEST_ASSERT(d->macroitems2.size(),(sizetypelist)1); + + { + // 2a.comparison - Test if the Action is correct? + d->macroitem2 = *d->macroitems2.constBegin(); + d->action2 = d->macroitem2->action(); + KOMACROTEST_ASSERT(assertActionsEqual(d->testaction,d->action2),true); + + // 3a.comparison - Test if the Variables have the correct number? + KOMACROTEST_ASSERT(d->macroitem2->variables().size(),(sizetypemap)4); + { + // 4a.comparison - Test if the Variables are equal. + KOMACROTEST_ASSERT(assertVariablesEqual(varstring, d->macroitem2->variable("teststring")), true); + KOMACROTEST_ASSERT(assertVariablesEqual(varint, d->macroitem2->variable("testint")), true); + KOMACROTEST_ASSERT(assertVariablesEqual(varbool, d->macroitem2->variable("testbool")), true); +// KOMACROTEST_ASSERT(assertVariablesEqual(vardouble, d->macroitem2->variable("testdouble")),true); + } + } + + // Now convert the parsen macro2 back to a QDomElement and again into macro3 for a better comparison. + const QDomElement elem2 = d->macro2->toXML(); + KOMACROTEST_ASSERT(d->macro3->parseXML(elem2),true); + + // Go down to the MacroItem of macro2. + d->macroitems3 = d->macro3->items(); + // 1b.comparison - Test if the MacroItems have the correct number? + KOMACROTEST_ASSERT(d->macroitems3.size(),(sizetypelist)1); + + { + // 2b.comparison - Test if the Action is correct? + d->macroitem3 = *d->macroitems3.constBegin(); + d->action3 = d->macroitem3->action(); + KOMACROTEST_ASSERT(assertActionsEqual(d->testaction,d->action3),true); + + // 3b.comparison - Test if the Variables have the correct number? + KOMACROTEST_ASSERT(d->macroitem3->variables().size(),(sizetypemap)4); + { + // 4b.comparison - Test if the Variables are equal. + KOMACROTEST_ASSERT(assertVariablesEqual(varstring, d->macroitem3->variable("teststring")), true); + KOMACROTEST_ASSERT(assertVariablesEqual(varint, d->macroitem3->variable("testint")), true); + KOMACROTEST_ASSERT(assertVariablesEqual(varbool, d->macroitem3->variable("testbool")), true); +// KOMACROTEST_ASSERT(assertVariablesEqual(vardouble, d->macroitem3->variable("testdouble")),true); + } + } +} + +// 11.Test - With a to big number. +void XMLHandlerTests2::testBigNumber() +{ + // Clear macroitems in the macros. + d->macro->clearItems(); + d->macro2->clearItems(); + d->macro3->clearItems(); + + // Part 1: From XML to a Macro. + // Test-XML-document with normal allocated variables. + const QString xml = QString("<!DOCTYPE macros>" + "<macro xmlversion=\"1\">" + "<item action=\"testaction\" >" + "<variable name=\"teststring\" >test_string</variable>" + "<variable name=\"testint\" >0123456789012345678901234567890123456789</variable>" + "<variable name=\"testbool\" >true</variable>" + "<variable name=\"testdouble\" >0.6</variable>" + "</item>" + "</macro>"); + // Set the XML-document with the above string. + QDomDocument doomdocument; + doomdocument.setContent(xml); + const QDomElement elem = doomdocument.documentElement(); + + // Create a MacroItem with the TestAction for macro2 and add it to macro. + d->macroitem = new KoMacro::MacroItem(); + d->macro->addItem(d->macroitem); + + d->macroitem->setAction(d->testaction); + + // Push the Variables into the macroitem. + KSharedPtr<KoMacro::Variable> varstring = d->macroitem->addVariable("teststring",QVariant("test_string")); + //TODO //KSharedPtr<KoMacro::Variable> varint = d->macroitem->addVariable("testint",QVariant(0123456789012345678901234567890123456789)); + KSharedPtr<KoMacro::Variable> varbool = d->macroitem->addVariable("testbool",QVariant(true)); + KSharedPtr<KoMacro::Variable> vardouble = d->macroitem->addVariable("testdouble",QVariant(0.6)); + + // Is our XML parseable into a 2. Macro by calling parseXML()? + KOMACROTEST_ASSERT(d->macro2->parseXML(elem),true); + + // Go down to the MacroItem of macro2. + d->macroitems2 = d->macro2->items(); + // 1a.comparison - Test if the MacroItems have the correct number? + KOMACROTEST_ASSERT(d->macroitems2.size(),(sizetypelist)1); + + { + // 2a.comparison - Test if the Action is correct? + d->macroitem2 = *d->macroitems2.constBegin(); + d->action2 = d->macroitem2->action(); + KOMACROTEST_ASSERT(assertActionsEqual(d->testaction,d->action2),true); + + // 3a.comparison - Test if the Variables have the correct number? + KOMACROTEST_ASSERT(d->macroitem2->variables().size(),(sizetypemap)4); + { + // 4a.comparison - Test if the Variables are equal. + KOMACROTEST_ASSERT(assertVariablesEqual(varstring, d->macroitem2->variable("teststring")), true); + //KOMACROTEST_ASSERT(assertVariablesEqual(varint, d->macroitem2->variable("testint")), true); + KOMACROTEST_ASSERT(assertVariablesEqual(varbool, d->macroitem2->variable("testbool")), true); + KOMACROTEST_ASSERT(assertVariablesEqual(vardouble, d->macroitem2->variable("testdouble")), true); + } + } + + // Now convert the parsen macro2 back to a QDomElement and again into macro3 for a better comparison. + const QDomElement elem2 = d->macro2->toXML(); + KOMACROTEST_ASSERT(d->macro3->parseXML(elem2),true); + + // Go down to the MacroItem of macro2. + d->macroitems3 = d->macro3->items(); + // 1b.comparison - Test if the MacroItems have the correct number? + KOMACROTEST_ASSERT(d->macroitems3.size(),(sizetypelist)1); + + { + // 2b.comparison - Test if the Action is correct? + d->macroitem3 = *d->macroitems3.constBegin(); + d->action3 = d->macroitem3->action(); + KOMACROTEST_ASSERT(assertActionsEqual(d->testaction,d->action3),true); + + // 3b.comparison - Test if the Variables have the correct number? + KOMACROTEST_ASSERT(d->macroitem3->variables().size(),(sizetypemap)4); + { + // 4b.comparison - Test if the Variables are equal. + KOMACROTEST_ASSERT(assertVariablesEqual(varstring, d->macroitem3->variable("teststring")), true); + //KOMACROTEST_ASSERT(assertVariablesEqual(varint, d->macroitem3->variable("testint")), true); + KOMACROTEST_ASSERT(assertVariablesEqual(varbool, d->macroitem3->variable("testbool")), true); + KOMACROTEST_ASSERT(assertVariablesEqual(vardouble, d->macroitem3->variable("testdouble")), true); + } + } +} + +// 12.Test - With two MacroItems. +void XMLHandlerTests2::testTwoMacroItems() +{ + // Clear macroitems in the macros. + d->macro->clearItems(); + d->macro2->clearItems(); + d->macro3->clearItems(); + + // Part 1: From XML to a Macro. + // Test-XML-document with normal allocated variables. + const QString xml = QString("<!DOCTYPE macros>" + "<macro xmlversion=\"1\">" + "<item action=\"testaction\" >" + "<variable name=\"teststring\" >test_string</variable>" + "<variable name=\"testint\" >0</variable>" + "<variable name=\"testbool\" >true</variable>" + "<variable name=\"testdouble\" >0.6</variable>" + "<variable name=\"testbla\" >somethingwrong</variable>" + "</item>" + "<item action=\"testaction\" >" + "<variable name=\"teststring\" >testString2</variable>" + "<variable name=\"testint\" >4</variable>" + "<variable name=\"testbool\" >false</variable>" + "<variable name=\"testdouble\" >0.7</variable>" + "<variable name=\"testbla\" >somethingwrong2</variable>" + "</item>" + "</macro>"); + // Set the XML-document with the above string. + QDomDocument doomdocument; + doomdocument.setContent(xml); + const QDomElement elem = doomdocument.documentElement(); + + // Create a MacroItem with the TestAction for macro2 and add it to macro. + d->macroitem = new KoMacro::MacroItem(); + d->macroitem_2 = new KoMacro::MacroItem(); + d->macro->addItem(d->macroitem); + d->macro->addItem(d->macroitem_2); + + d->macroitem->setAction(d->testaction); + d->macroitem_2->setAction(d->testaction_2); + + // Push the Variables into the macroitem. + KSharedPtr<KoMacro::Variable> varstring = d->macroitem->addVariable("teststring",QVariant("test_string")); + KSharedPtr<KoMacro::Variable> varint = d->macroitem->addVariable("testint",QVariant(0)); + KSharedPtr<KoMacro::Variable> varbool = d->macroitem->addVariable("testbool",QVariant(true)); + KSharedPtr<KoMacro::Variable> vardouble = d->macroitem->addVariable("testdouble",QVariant(0.6)); + KSharedPtr<KoMacro::Variable> varbla = d->macroitem->addVariable("testbla","somethingwrong"); + + // Push the Variables into the macroitem4. + KSharedPtr<KoMacro::Variable> varstring_2 = d->macroitem_2->addVariable("teststring",QVariant("testString2")); + KSharedPtr<KoMacro::Variable> varint_2 = d->macroitem_2->addVariable("testint",QVariant(4)); + KSharedPtr<KoMacro::Variable> varbool_2 = d->macroitem_2->addVariable("testbool",QVariant(false)); + KSharedPtr<KoMacro::Variable> vardouble_2 = d->macroitem_2->addVariable("testdouble",QVariant(0.7)); + KSharedPtr<KoMacro::Variable> varbla_2 = d->macroitem_2->addVariable("testbla","somethingwrong2"); + + // Is our XML parseable into a 2. Macro by calling parseXML()? + KOMACROTEST_ASSERT(d->macro2->parseXML(elem),true); + + // Go down to the MacroItem of macro2. + d->macroitems2 = d->macro2->items(); + // 1a.comparison - Test if the MacroItems have the correct number? + KOMACROTEST_ASSERT(d->macroitems2.size(),(sizetypelist)2); + + { + QValueList<KSharedPtr<KoMacro::MacroItem > >::ConstIterator mit2(d->macroitems2.constBegin()); + // 2a.comparison - Test if the Action is correct? + d->macroitem2 = *mit2; + mit2++; + d->macroitem2_2 = *mit2; + d->action2 = d->macroitem2->action(); + d->action2_2 = d->macroitem2_2->action(); + KOMACROTEST_ASSERT(assertActionsEqual(d->testaction,d->action2),true); + KOMACROTEST_ASSERT(assertActionsEqual(d->testaction_2,d->action2_2),true); + + // 3a.comparison - Test if the Variables have the correct number? + KOMACROTEST_ASSERT(d->macroitem2->variables().size(),(sizetypemap)5); + KOMACROTEST_ASSERT(d->macroitem2_2->variables().size(),(sizetypemap)5); + { + // 4a.comparison - Test if the Variables are equal. + KOMACROTEST_ASSERT(assertVariablesEqual(varstring, d->macroitem2->variable("teststring")),true); + KOMACROTEST_ASSERT(assertVariablesEqual(varint, d->macroitem2->variable("testint")), true); + KOMACROTEST_ASSERT(assertVariablesEqual(varbool, d->macroitem2->variable("testbool")), true); + KOMACROTEST_ASSERT(assertVariablesEqual(vardouble, d->macroitem2->variable("testdouble")),true); + KOMACROTEST_ASSERT(assertVariablesEqual(varbla, d->macroitem2->variable("testbla")),true); + + KOMACROTEST_ASSERT(assertVariablesEqual(varstring_2,d->macroitem2_2->variable("teststring")),true); + KOMACROTEST_ASSERT(assertVariablesEqual(varint_2, d->macroitem2_2->variable("testint")), true); + KOMACROTEST_ASSERT(assertVariablesEqual(varbool_2, d->macroitem2_2->variable("testbool")), true); + KOMACROTEST_ASSERT(assertVariablesEqual(vardouble_2,d->macroitem2_2->variable("testdouble")),true); + KOMACROTEST_ASSERT(assertVariablesEqual(varbla_2, d->macroitem2_2->variable("testbla")),true); + } + } + + // Now convert the parsen macro2 back to a QDomElement and again into macro3 for a better comparison. + const QDomElement elem2 = d->macro2->toXML(); + KOMACROTEST_ASSERT(d->macro3->parseXML(elem2),true); + + // Go down to the MacroItem of macro2. + d->macroitems3 = d->macro3->items(); + // 1b.comparison - Test if the MacroItems have the correct number? + KOMACROTEST_ASSERT(d->macroitems3.size(),(sizetypelist)2); + + { + QValueList<KSharedPtr<KoMacro::MacroItem > >::ConstIterator mit3(d->macroitems3.constBegin()); + // 2b.comparison - Test if the Action is correct? + d->macroitem3 = *mit3; + mit3++; + d->macroitem3_2 = *mit3; + d->action3 = d->macroitem3->action(); + d->action3_2 = d->macroitem3_2->action(); + KOMACROTEST_ASSERT(assertActionsEqual(d->testaction,d->action3),true); + KOMACROTEST_ASSERT(assertActionsEqual(d->testaction,d->action3_2),true); + + // 3b.comparison - Test if the Variables have the correct number? + KOMACROTEST_ASSERT(d->macroitem3->variables().size(),(sizetypemap)5); + KOMACROTEST_ASSERT(d->macroitem3_2->variables().size(),(sizetypemap)5); + { + // 4b.comparison - Test if the Variables are equal. + KOMACROTEST_ASSERT(assertVariablesEqual(varstring, d->macroitem3->variable("teststring")),true); + KOMACROTEST_ASSERT(assertVariablesEqual(varint, d->macroitem3->variable("testint")), true); + KOMACROTEST_ASSERT(assertVariablesEqual(varbool, d->macroitem3->variable("testbool")), true); + KOMACROTEST_ASSERT(assertVariablesEqual(vardouble, d->macroitem3->variable("testdouble")),true); + KOMACROTEST_ASSERT(assertVariablesEqual(varbla, d->macroitem3->variable("testbla")),true); + + KOMACROTEST_ASSERT(assertVariablesEqual(varstring_2,d->macroitem3_2->variable("teststring")),true); + KOMACROTEST_ASSERT(assertVariablesEqual(varint_2, d->macroitem3_2->variable("testint")), true); + KOMACROTEST_ASSERT(assertVariablesEqual(varbool_2, d->macroitem3_2->variable("testbool")), true); + KOMACROTEST_ASSERT(assertVariablesEqual(vardouble_2,d->macroitem3_2->variable("testdouble")),true); + KOMACROTEST_ASSERT(assertVariablesEqual(varbla_2, d->macroitem3_2->variable("testbla")),true); + } + } +} + +/*************************************************************************** +* End of Sub-methos of testParseAndToXML2(). +***************************************************************************/ + +bool XMLHandlerTests2::assertActionsEqual(KSharedPtr<KoMacro::Action> action, + KSharedPtr<KoMacro::Action> action2) +{ + return action->name() == action2->name(); +} + +bool XMLHandlerTests2::assertVariablesEqual(KSharedPtr<KoMacro::Variable> var, + KSharedPtr<KoMacro::Variable> var2) +{ + if ( var->variant() != var2->variant() ) kdDebug() << "Variable1: " << var->variant() << " and Variable2: " << var2->variant() << endl; + return var->variant() == var2->variant(); +} + +#include "xmlhandlertests2.moc" diff --git a/kexi/plugins/macros/tests/xmlhandlertests2.h b/kexi/plugins/macros/tests/xmlhandlertests2.h new file mode 100644 index 00000000..0a3fee3a --- /dev/null +++ b/kexi/plugins/macros/tests/xmlhandlertests2.h @@ -0,0 +1,132 @@ +/*************************************************************************** + * This file is part of the KDE project + * copyright (C) 2006 by Bernd Steindorff (bernd@itii.de) + * + * 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 KOMACROTEST_XMLHandlerTests2_H +#define KOMACROTEST_XMLHandlerTests2_H + +#include <kunittest/tester.h> +#include "../lib/macro.h" + +namespace KoMacroTest { + + /** + * The common testsuite used to test common @a KoMacro + * functionality. + */ + class XMLHandlerTests2 : public KUnitTest::SlotTester + { + Q_OBJECT + public: + + /** + * Constructor. + */ + XMLHandlerTests2(); + + /** + * Destructor. + */ + virtual ~XMLHandlerTests2(); + + public slots: + + /** + * This slot got called by KUnitTest before testing + * starts. + */ + void setUp(); + + /** + * This slot got called by KUnitTest after all tests + * are done. + */ + void tearDown(); + + /** + * Test the @a KoMacro::XMLHandler parseXML()-function. + */ + void testParseAndToXML(); + + private: + /// @internal d-pointer class. + class Private; + /// @internal d-pointer instance. + Private* const d; + + typedef QMap<QString,KoMacro::Variable>::size_type sizetypemap; + typedef QValueList<KSharedPtr<KoMacro::MacroItem > >::size_type sizetypelist; + + /** + * Compares a XML-Element with a Macro. Call sub-asserts. + * @p macro The parsen @a Macro. + * @p domelement The given @a QDomElement which is parsen. + * @p isitemsempty Bool for expectation of an empty @a MacroItem -List. + * @p isactionset Bool for expectation that the @a Action -names are equal. + * @p isvariableok QMap of Bools for comparing each @a Variable . + */ +/* void assertMacroContentEqToXML(const KSharedPtr<KoMacro::Macro> macro, + const QDomElement& elem, + const bool isitemsempty, + const bool isactionset, + const QMap<QString, bool> isvariableok); + + // Prints a QMap of Variables to kdDebug(). + void printMvariables(const QMap<QString, KSharedPtr<KoMacro::Variable > > mvariables, const QString s); +*/ + /** + * Sub-methods of testParseXML() and testToXML(). + * Test the correct parsing of a @a QDomElement into a @a Macro + * respectively expected failure of parsing. Then transform it + * back and compare it. + */ + // 1.Test - Correct DomElement. + void testCorrectDomElement(); + // 2.Test - XML-document with bad root element. + void testBadRoot(); + // 3.Test - XML-document with a missing Variable. + void testMissingVariable(); + // 4.Test - One more Variable in XML-Document. + void testMoreVariables(); + // 5.Test - XML-document with wrong macro-xmlversion. + void testWrongVersion(); + // 6.Test - XML-document if it has a wrong structure like + // wrong parathesis or missing end tag. + void testWrongXMLStruct(); + // 7.Test-XML-document with maximum field-size. + void testMaxNum(); + // 8.Test-XML-document with maximum+1 field-size. + void testMaxNum2(); + // 9.Test-XML-document with minimum field-size. + void testMinNum(); + // 10.Test-XML-document with minimum-1 field-size. + void testMinNum2(); + // 11.Test - With a to big number. + void testBigNumber(); + // 12.Test - With two MacroItems. + void testTwoMacroItems(); + + + bool assertActionsEqual(KSharedPtr<KoMacro::Action> action, + KSharedPtr<KoMacro::Action> action2); + + bool assertVariablesEqual(KSharedPtr<KoMacro::Variable> var, + KSharedPtr<KoMacro::Variable> var2); + }; +} + +#endif diff --git a/kexi/plugins/migration/Makefile.am b/kexi/plugins/migration/Makefile.am new file mode 100644 index 00000000..e496773e --- /dev/null +++ b/kexi/plugins/migration/Makefile.am @@ -0,0 +1,20 @@ +include $(top_srcdir)/kexi/Makefile.global + +kde_module_LTLIBRARIES = kexihandler_migration.la + +kexihandler_migration_la_SOURCES = keximigrationpart.cpp + +kexihandler_migration_la_LDFLAGS = $(all_libraries) $(KDE_PLUGIN) $(VER_INFO) -module +kexihandler_migration_la_LIBADD = ../../core/libkexicore.la \ + ../../migration/libkeximigrate.la + +INCLUDES= -I$(top_srcdir)/kexi/core -I$(top_srcdir)/kexi \ + -I$(top_srcdir)/kexi/widget -I$(top_srcdir)/kexi/migration \ + -I$(top_srcdir)/kexi/kexiDB $(all_includes) + +METASOURCES = AUTO + +servicesdir=$(kde_servicesdir)/kexi +services_DATA=keximigrationhandler.desktop + +include ../Makefile.common diff --git a/kexi/plugins/migration/keximigrationhandler.desktop b/kexi/plugins/migration/keximigrationhandler.desktop new file mode 100644 index 00000000..4ca99b05 --- /dev/null +++ b/kexi/plugins/migration/keximigrationhandler.desktop @@ -0,0 +1,102 @@ +[Desktop Entry] +Type=Service +ServiceTypes=Kexi/Handler + +GenericName=Migration Plugin +GenericName[bg]=Приставка за мигриране +GenericName[ca]=Connector de migració +GenericName[cy]=Ategyn Mudo +GenericName[da]=Migrationsplugin +GenericName[de]=Migrations-Modul +GenericName[el]=Πρόσθετο μεταφοράς +GenericName[eo]=Migradkromaĵo +GenericName[es]=Complemento para migración +GenericName[et]=Migreerumisplugin +GenericName[eu]=Migraziorako plugina +GenericName[fa]=وصلۀ جابهجایی +GenericName[fi]=Yhdistämisliitännäinen +GenericName[fr]=Module de migration +GenericName[fy]=Migraasjeplugin +GenericName[ga]=Breiseán Migration +GenericName[gl]=Plugin de Migración +GenericName[he]=תוסף Migration +GenericName[hr]=Migracijski dodatak +GenericName[hu]=Migrálási modul +GenericName[is]=Gagnaflutnings íforrit +GenericName[it]=Plugin di migrazione +GenericName[ja]=データ移行プラグイン +GenericName[km]=កម្មវិធីជំនួយផ្លាស់ប្ដូរកន្លែង +GenericName[lv]=Migrācijas spraudnis +GenericName[ms]=Plugin Migrasi +GenericName[nb]=Programtillegg for migrering +GenericName[nds]=Datenutlagern-Moduul +GenericName[ne]=माइग्रेसन प्लगइन +GenericName[nl]=Migratieplugin +GenericName[nn]=Programtillegg for migrering +GenericName[pl]=Wtyczka migracji +GenericName[pt]='Plugin' de Migração +GenericName[pt_BR]=Plugin de Migração +GenericName[ru]=Миграция +GenericName[sk]=Modul pre migráciu +GenericName[sl]=Vstavek za prehod +GenericName[sr]=Миграциони прикључак +GenericName[sr@Latn]=Migracioni priključak +GenericName[sv]=Övergångsinsticksprogram +GenericName[uk]=Втулок міграції +GenericName[uz]=Migratsiya plagini +GenericName[uz@cyrillic]=Миграция плагини +GenericName[zh_CN]=升迁插件 +GenericName[zh_TW]=轉移外掛程式 +Name=Migration Plugin +Name[bg]=Приставка за мигриране +Name[ca]=Connector de migració +Name[cy]=Ategyn Mudo +Name[da]=Migrationsplugin +Name[de]=Migrations-Modul +Name[el]=Πρόσθετο μεταφοράς +Name[eo]=Migradkromaĵo +Name[es]=Complemento para migración +Name[et]=Migreerumisplugin +Name[eu]=Migraziorako plugina +Name[fa]=وصلۀ جابهجایی +Name[fi]=Yhdistämisliitännäinen +Name[fr]=Module de migration +Name[fy]=Migrationplugin +Name[ga]=Breiseán Migration +Name[gl]=Plugin de Migración +Name[he]=תוסף Migration +Name[hi]=माइग्रेशन प्लगइन +Name[hr]=Migracijski dodatak +Name[hu]=Migrálási modul +Name[is]=Gagnaflutnings íforrit +Name[it]=Plugin di migrazione +Name[ja]=データ移行プラグイン +Name[km]=កម្មវិធីជំនួយសម្រាប់ផ្លាស់ប្ដូរ +Name[lv]=Migrācijas spraudnis +Name[ms]=Plugin Migrasi +Name[nb]=Programtillegg for migrering +Name[nds]=Datenutlagern-Moduul +Name[ne]=माइग्रेसन प्लगइन +Name[nl]=Migratieplugin +Name[nn]=Programtillegg for migrering +Name[pl]=Wtyczka migracji +Name[pt]='Plugin' de Migração +Name[pt_BR]=Plugin de Migração +Name[ru]=Модуль миграции +Name[sk]=Modul pre migráciu +Name[sl]=Vstavek za prehod +Name[sr]=Миграциони прикључак +Name[sr@Latn]=Migracioni priključak +Name[sv]=Övergångsinsticksprogram +Name[uk]=Втулок міграції +Name[uz]=Migratsiya plagini +Name[uz@cyrillic]=Миграция плагини +Name[zh_CN]=升迁插件 +Name[zh_TW]=轉移外掛程式 +X-KDE-Library=kexihandler_migration +X-KDE-ParentApp=kexi +X-Kexi-PartVersion=2 +X-Kexi-TypeName=migration +X-Kexi-GroupIcon=migration +X-Kexi-ItemIcon=migration +X-Kexi-NoObject=true diff --git a/kexi/plugins/migration/keximigrationpart.cpp b/kexi/plugins/migration/keximigrationpart.cpp new file mode 100644 index 00000000..0f6c408b --- /dev/null +++ b/kexi/plugins/migration/keximigrationpart.cpp @@ -0,0 +1,46 @@ +/* This file is part of the KDE project + Copyright (C) 2004 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 "keximigrationpart.h" + +#include <migration/importwizard.h> + +#include <kgenericfactory.h> + +KexiMigrationPart::KexiMigrationPart(QObject *parent, const char *name, const QStringList &args) + : KexiInternalPart(parent, name, args) +{ +} + +KexiMigrationPart::~KexiMigrationPart() +{ +} + +QWidget *KexiMigrationPart::createWidget(const char* /*widgetClass*/, KexiMainWindow* mainWin, + QWidget *parent, const char *objName, QMap<QString,QString>* args ) +{ + Q_UNUSED( mainWin ); + + KexiMigration::ImportWizard *w = new KexiMigration::ImportWizard(parent, args); + w->setName(objName); + return w; +} + +K_EXPORT_COMPONENT_FACTORY( kexihandler_migration, + KGenericFactory<KexiMigrationPart>("kexihandler_migration") ) diff --git a/kexi/plugins/migration/keximigrationpart.h b/kexi/plugins/migration/keximigrationpart.h new file mode 100644 index 00000000..528aac82 --- /dev/null +++ b/kexi/plugins/migration/keximigrationpart.h @@ -0,0 +1,38 @@ +/* This file is part of the KDE project + Copyright (C) 2004 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 KEXI_MIGRATION_PART_H +#define KEXI_MIGRATION_PART_H + +#include <core/kexiinternalpart.h> + +/*! @short Internal part for data/project migration wizard. */ +class KexiMigrationPart : public KexiInternalPart +{ + public: + KexiMigrationPart(QObject *parent, const char *name, const QStringList &args); + virtual ~KexiMigrationPart(); + + /*! Reimplement this if your internal part has to return widgets + or QDialog objects. */ + virtual QWidget *createWidget(const char* /*widgetClass*/, KexiMainWindow* mainWin, + QWidget *parent, const char *objName = 0, QMap<QString,QString>* args = 0); +}; + +#endif 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 + diff --git a/kexi/plugins/relations/Makefile.am b/kexi/plugins/relations/Makefile.am new file mode 100644 index 00000000..6e35ab3a --- /dev/null +++ b/kexi/plugins/relations/Makefile.am @@ -0,0 +1,28 @@ +include $(top_srcdir)/kexi/Makefile.global + +kde_module_LTLIBRARIES = kexihandler_relation.la + +#kexihandler_relation_la_SOURCES = kexirelationhandler.cpp kexirelationhandlerproxy.cpp kexirelationview.cpp \ +# kexirelationviewtable.cpp kexirelationdialog.cpp \ +# kexirelationviewconnection.cpp +kexihandler_relation_la_SOURCES = kexirelationpartimpl.cpp \ + kexirelationmaindlg.cpp + +kexihandler_relation_la_LDFLAGS = $(all_libraries) $(KDE_PLUGIN) $(VER_INFO) -module +kexihandler_relation_la_LIBADD = ../../core/libkexicore.la \ + ../../widget/relations/libkexirelationsview.la + +INCLUDES= -I$(top_srcdir)/kexi/core -I$(top_srcdir)/kexi \ + -I$(top_srcdir)/kexi/widget -I$(top_srcdir)/kexi/widget/relations \ + -I$(top_srcdir)/kexi/tableview \ + -I$(top_srcdir)/kexi/kexidb $(all_includes) + +servicesdir=$(kde_servicesdir)/kexi +services_DATA=kexirelationhandler.desktop + +rcdir = $(kde_datadir)/kexi +rc_DATA = kexirelationpartui.rc kexirelationpartinstui.rc + +METASOURCES = AUTO + +include ../Makefile.common diff --git a/kexi/plugins/relations/kexirelationhandler.desktop b/kexi/plugins/relations/kexirelationhandler.desktop new file mode 100644 index 00000000..7232d316 --- /dev/null +++ b/kexi/plugins/relations/kexirelationhandler.desktop @@ -0,0 +1,110 @@ +[Desktop Entry] +Type=Service +ServiceTypes=Kexi/Handler + +GenericName=Relationships +GenericName[bg]=Релации +GenericName[ca]=Relacions +GenericName[cs]=Vztahy +GenericName[cy]=Perthnasau +GenericName[de]=Beziehungen +GenericName[el]=Συσχετίσεις +GenericName[eo]=Rilatoj +GenericName[es]=Relaciones +GenericName[et]=Sõltuvused +GenericName[eu]=Erlazioak +GenericName[fa]=روابط +GenericName[fi]=Yhteyssuhteet +GenericName[fr]=Relations +GenericName[fy]=Relaasjes +GenericName[ga]=Gaolta +GenericName[gl]=Relacións +GenericName[he]=יחסים +GenericName[hi]=रिलेशनशिप +GenericName[hr]=Poveznice +GenericName[hu]=Kapcsolatok +GenericName[is]=Tengsl +GenericName[it]=Relazioni +GenericName[ja]=リレーションシップ +GenericName[km]=ទំនាក់ទំនង +GenericName[lv]=Relācijas +GenericName[ms]=Hubungan +GenericName[nb]=Relasjoner +GenericName[nds]=Betöög +GenericName[ne]=सम्बन्धहरू +GenericName[nl]=Relaties +GenericName[nn]=Relasjonar +GenericName[pl]=Relacje +GenericName[pt]=Relações +GenericName[pt_BR]=Relacionamentos +GenericName[ru]=Связи +GenericName[se]=Relašuvnnat +GenericName[sk]=Vzťahy +GenericName[sl]=Razmerja +GenericName[sr]=Односи +GenericName[sr@Latn]=Odnosi +GenericName[sv]=Förhållanden +GenericName[ta]=உறவுமுறைகள் +GenericName[tr]=İlişkiler +GenericName[uk]=Взаємозвязки +GenericName[uz]=Aloqalar +GenericName[uz@cyrillic]=Алоқалар +GenericName[zh_CN]=关系 +GenericName[zh_TW]=關係 +Name=Relationships +Name[bg]=Релации +Name[ca]=Relacions +Name[cs]=Vztahy +Name[cy]=Perthnasau +Name[de]=Beziehungen +Name[el]=Συσχετίσεις +Name[eo]=Rilatoj +Name[es]=Relaciones +Name[et]=Sõltuvused +Name[eu]=Erlazioak +Name[fa]=روابط +Name[fi]=Yhteydet +Name[fr]=Relations +Name[fy]=Relaasjes +Name[ga]=Gaolta +Name[gl]=Relacións +Name[he]=יחסים +Name[hi]=रिलेशनशिप +Name[hr]=Poveznice +Name[hu]=Kapcsolatok +Name[is]=Tengsl +Name[it]=Relazioni +Name[ja]=リレーションシップ +Name[km]=ទំនាក់ទំនង +Name[lv]=Relācijas +Name[ms]=Hubungan +Name[nb]=Relasjoner +Name[nds]=Betöög +Name[ne]=सम्बन्धहरू +Name[nl]=Relaties +Name[nn]=Relasjonar +Name[pl]=Relacje +Name[pt]=Relações +Name[pt_BR]=Relações +Name[ru]=Взаимосвязи +Name[se]=Relašuvnnat +Name[sk]=Vzťahy +Name[sl]=Razmerja +Name[sr]=Односи +Name[sr@Latn]=Odnosi +Name[sv]=Förhållanden +Name[ta]=உறவுகள் +Name[tg]=Муносибатҳо +Name[tr]=İlişkiler +Name[uk]=Взаємозвязки +Name[uz]=Aloqalar +Name[uz@cyrillic]=Алоқалар +Name[zh_CN]=关系 +Name[zh_TW]=關係 +X-KDE-Library=kexihandler_relation +X-KDE-ParentApp=kexi +X-Kexi-PartVersion=2 +X-Kexi-TypeName=relation +X-Kexi-TypeMime=kexi/relation +X-Kexi-ItemIcon=relations +X-Kexi-NoObject=true diff --git a/kexi/plugins/relations/kexirelationmaindlg.cpp b/kexi/plugins/relations/kexirelationmaindlg.cpp new file mode 100644 index 00000000..6b14fffa --- /dev/null +++ b/kexi/plugins/relations/kexirelationmaindlg.cpp @@ -0,0 +1,81 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Lucijan Busch <lucijan@kde.org> + + 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 "kexirelationmaindlg.h" + +#include <klocale.h> +#include <kdebug.h> +#include <kiconloader.h> + +#include <qlayout.h> + +#include <kexidb/connection.h> + +#include "keximainwindow.h" +#include "kexiproject.h" +#include "kexirelationwidget.h" +#include "kexirelationview.h" + +KexiRelationMainDlg::KexiRelationMainDlg(KexiMainWindow *mainWin, QWidget *parent, const char *name) + : KexiViewBase(mainWin, parent, name) +{ + kdDebug() << "KexiRelationMainDlg()" << endl; +// setIcon(SmallIcon("relation")); + m_defaultIconName = "relation"; + setCaption( i18n("Relationships") ); +// setDocID( win->generatePrivateDocID() ); + + m_rel = new KexiRelationWidget(mainWin, this); + //the view can receive some our actions + addActionProxyChild( m_rel ); +// addActionProxyChild( m_view->relationView() ); + + QVBoxLayout *g = new QVBoxLayout(this); + g->addWidget(m_rel); + + //show all tables + KexiDB::Connection *conn = mainWin->project()->dbConnection(); + QStringList tables = conn->tableNames(); + for (QStringList::ConstIterator it = tables.constBegin(); it!=tables.constEnd(); ++it) { + m_rel->addTable( *it ); + } +} + +KexiRelationMainDlg::~KexiRelationMainDlg() +{ +} + +QSize KexiRelationMainDlg::sizeHint() const +{ + return QSize(600,300); +} + +QWidget* +KexiRelationMainDlg::mainWidget() +{ + return m_rel; +} + +QString KexiRelationMainDlg::itemIcon() +{ + return "relation"; +} + +#include "kexirelationmaindlg.moc" + diff --git a/kexi/plugins/relations/kexirelationmaindlg.h b/kexi/plugins/relations/kexirelationmaindlg.h new file mode 100644 index 00000000..791d6544 --- /dev/null +++ b/kexi/plugins/relations/kexirelationmaindlg.h @@ -0,0 +1,47 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Lucijan Busch <lucijan@kde.org> + + 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 KEXIRELATIONMAINDLG_H +#define KEXIRELATIONMAINDLG_H + +#include <kexiviewbase.h> + +class KexiMainWindow; +class KexiRelationWidget; + +class KexiRelationMainDlg : public KexiViewBase +{ + Q_OBJECT + + public: + KexiRelationMainDlg(KexiMainWindow *mainWin, QWidget *parent, const char *name = 0); + ~KexiRelationMainDlg(); + + virtual QSize sizeHint() const; + + virtual QWidget* mainWidget(); + + virtual QString itemIcon(); + + private: + KexiRelationWidget *m_rel; +}; + +#endif + diff --git a/kexi/plugins/relations/kexirelationpartimpl.cpp b/kexi/plugins/relations/kexirelationpartimpl.cpp new file mode 100644 index 00000000..a2a7c213 --- /dev/null +++ b/kexi/plugins/relations/kexirelationpartimpl.cpp @@ -0,0 +1,85 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Lucijan Busch <lucijan@kde.org> + + 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 "kexirelationmaindlg.h" +#include "kexirelationpartimpl.h" + +#include <kexirelationwidget.h> +#include <kexidialogbase.h> +#include <keximainwindow.h> + +#include <kgenericfactory.h> +#include <kiconloader.h> +#include <kdebug.h> + +KexiRelationPartImpl::KexiRelationPartImpl(QObject *parent, const char *name, const QStringList &args) + : KexiInternalPart(parent, name, args) +{ + kdDebug() << "KexiRelationPartImpl()" << endl; +} + +KexiRelationPartImpl::~KexiRelationPartImpl() +{ +} + +/*QWidget * +KexiRelationPartImpl::createWidget(const char* , KexiMainWindow* mainWin, + QWidget *parent, const char *objName) +{ + return new KexiRelationWidget(mainWin, parent, objName); +}*/ + +/*KexiDialogBase * +KexiRelationPartImpl::createDialog(KexiMainWindow* mainWin, const char *) +{ + kdDebug() << "KexiRelationPartImpl::createDialog()" << endl; + KexiDialogBase * dlg = new KexiDialogBase(mainWin, i18n("Relations")); + dlg->setIcon(SmallIcon("relation")); + dlg->setDocID( mainWin->generatePrivateDocID() ); + + KexiRelationMainDlg *view = new KexiRelationMainDlg(mainWin, 0, "relations"); + dlg->addView(view); +// dlg->show(); +// dlg->registerDialog(); + + return dlg; +}*/ + +KexiViewBase * +KexiRelationPartImpl::createView(KexiMainWindow* mainWin, QWidget *parent, const char *) +{ +// kdDebug() << "KexiRelationPartImpl::createDialog()" << endl; +// KexiDialogBase * dlg = new KexiDialogBase(mainWin, i18n("Relations")); +// dlg->setIcon(SmallIcon("relation")); +// dlg->setDocID( mainWin->generatePrivateDocID() ); + + KexiRelationMainDlg *view = new KexiRelationMainDlg(mainWin, parent, "relations"); +// dlg->addView(view); +// dlg->show(); +// dlg->registerDialog(); + + return view; +} + + +K_EXPORT_COMPONENT_FACTORY( kexihandler_relation, + KGenericFactory<KexiRelationPartImpl>("kexihandler_relation") ) + +#include "kexirelationpartimpl.moc" + diff --git a/kexi/plugins/relations/kexirelationpartimpl.h b/kexi/plugins/relations/kexirelationpartimpl.h new file mode 100644 index 00000000..b5b5438e --- /dev/null +++ b/kexi/plugins/relations/kexirelationpartimpl.h @@ -0,0 +1,46 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Lucijan Busch <lucijan@kde.org> + + 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 KEXIRELATIONPARTIMPL_H +#define KEXIRELATIONPARTIMPL_H + +#include <kexiinternalpart.h> + +class KexiRelationPartImpl : public KexiInternalPart +{ + Q_OBJECT + + public: + KexiRelationPartImpl(QObject *parent, const char *name, const QStringList &args); + virtual ~KexiRelationPartImpl(); + + protected: +// virtual QWidget *createWidget(const char* widgetClass, KexiMainWindow* mainWin, +// QWidget *parent, const char *objName=0); + + virtual KexiViewBase *createView(KexiMainWindow* mainWin, QWidget *parent, + const char *objName=0); + + //virtual KexiDialogBase *createWindow(KexiMainWindow *parent); + //virtual QWidget *createWidget(QWidget *parent, KexiMainWindow *win); +}; + +#endif + + diff --git a/kexi/plugins/relations/kexirelationpartinstui.rc b/kexi/plugins/relations/kexirelationpartinstui.rc new file mode 100644 index 00000000..cad23d56 --- /dev/null +++ b/kexi/plugins/relations/kexirelationpartinstui.rc @@ -0,0 +1,6 @@ +<!DOCTYPE kpartgui> +<kpartgui name="kexirelationpartinst" version="1"> + +<!-- TODO --> + +</kpartgui> diff --git a/kexi/plugins/relations/kexirelationpartui.rc b/kexi/plugins/relations/kexirelationpartui.rc new file mode 100644 index 00000000..67002e71 --- /dev/null +++ b/kexi/plugins/relations/kexirelationpartui.rc @@ -0,0 +1,14 @@ +<!DOCTYPE kpartgui> +<kpartgui name="kexirelationpart" version="2"> +<MenuBar> + <Menu name="project"> + <text>&Project</text> + <Action name="relations"/> + </Menu> +</MenuBar> + +<ToolBar name="projectToolBar" fullWidth="false"> + <text>Project</text> + <Action name="relations"/> +</ToolBar> +</kpartgui> diff --git a/kexi/plugins/reports/Makefile.am b/kexi/plugins/reports/Makefile.am new file mode 100644 index 00000000..4f8e54c4 --- /dev/null +++ b/kexi/plugins/reports/Makefile.am @@ -0,0 +1,51 @@ +include $(top_srcdir)/kexi/Makefile.global + +kde_module_LTLIBRARIES = kexihandler_report.la kexireportwidgets.la + +kexihandler_report_la_SOURCES = kexireports.cpp +kexihandler_report_la_LDFLAGS = $(all_libraries) $(KDE_PLUGIN) $(VER_INFO) -module -no-undefined +kexihandler_report_la_LIBADD = ../../core/libkexicore.la \ + $(top_builddir)/kexi/widget/utils/libkexiguiutils.la \ + $(top_builddir)/kexi/widget/libkexiextendedwidgets.la \ + $(top_builddir)/kexi/formeditor/libkformdesigner.la \ + $(top_builddir)/kexi/plugins/forms/libkexiformutils.la \ + ./libkexireportutils.la + +kexireportwidgets_la_LDFLAGS = $(all_libraries) $(KDE_PLUGIN) $(VER_INFO) -module -no-undefined +kexireportwidgets_la_SOURCES = reportwidgets.cpp kexireportfactory.cpp +kexireportwidgets_la_LIBADD = $(top_builddir)/kexi/formeditor/libkformdesigner.la \ + $(top_builddir)/kexi/plugins/forms/libkexiformutils.la \ + $(top_builddir)/kexi/plugins/forms/widgets/libkexiformutilswidgets.la \ + $(top_builddir)/kexi/widget/libkexiextendedwidgets.la \ + ./libkexireportutils.la + +lib_LTLIBRARIES = libkexireportutils.la +libkexireportutils_la_SOURCES = \ + kexireportpart.cpp kexireportview.cpp kexireportform.cpp +libkexireportutils_la_LDFLAGS = $(all_libraries) $(VER_INFO) -no-undefined +libkexireportutils_la_LIBADD = $(top_builddir)/kexi/core/libkexicore.la \ + $(top_builddir)/kexi/formeditor/libkformdesigner.la \ + $(top_builddir)/kexi/plugins/forms/widgets/libkexiformutilswidgets.la \ + $(top_builddir)/kexi/widget/libkexiextendedwidgets.la \ + $(top_builddir)/kexi/plugins/forms/libkexiformutils.la + +kformdesignerservicesdir=$(kde_servicesdir)/kformdesigner +kformdesignerservices_DATA = kformdesigner_kexireportfactory.desktop + +servicesdir=$(kde_servicesdir)/kexi +services_DATA=kexireporthandler.desktop + +rcdir = $(kde_datadir)/kexi +rc_DATA = kexireportpartui.rc kexireportpartinstui.rc + +SUBDIRS = . + +INCLUDES= -I$(top_srcdir)/kexi/core -I$(top_srcdir)/kexi \ + -I$(top_srcdir)/lib -I$(top_srcdir)/lib/kofficecore \ + -I$(top_srcdir)/kexi/widget/utils \ + -I$(top_srcdir)/kexi/widget \ + -I$(top_srcdir)/kexi/formeditor -I$(top_srcdir)/kexi/plugins/forms $(all_includes) + +METASOURCES = AUTO + +include ../Makefile.common diff --git a/kexi/plugins/reports/kexireportfactory.cpp b/kexi/plugins/reports/kexireportfactory.cpp new file mode 100644 index 00000000..0ac782c4 --- /dev/null +++ b/kexi/plugins/reports/kexireportfactory.cpp @@ -0,0 +1,227 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr> + + 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 <qpopupmenu.h> +#include <qvaluevector.h> + +#include <kgenericfactory.h> +#include <klocale.h> +#include <kiconloader.h> +#include <kdebug.h> +#include <klineedit.h> + +#include <container.h> +#include <form.h> +#include <formmanager.h> +#include <widgetlibrary.h> + +#include "reportwidgets.h" +#include "kexireportfactory.h" + +KexiReportFactory::KexiReportFactory(QObject *parent, const char *name, const QStringList &) + : KFormDesigner::WidgetFactory(parent, name) +{ + KFormDesigner::WidgetInfo *wView = new KFormDesigner::WidgetInfo(this); + wView->setPixmap("report"); + wView->setClassName("KexiReportForm"); + wView->setName(i18n("Report")); + wView->setNamePrefix( + i18n("Widget name. This string will be used to name widgets of this class. It must _not_ contain white spaces and non latin1 characters.", "report")); + wView->setDescription(i18n("A report")); + addClass(wView); + + KFormDesigner::WidgetInfo *wLabel = new KFormDesigner::WidgetInfo(this); + wLabel->setPixmap("label"); + wLabel->setClassName("Label"); + wLabel->setName(i18n("Label")); + wLabel->setNamePrefix( + i18n("Widget name. This string will be used to name widgets of this class. It must _not_ contain white spaces and non latin1 characters.", "label")); + wLabel->setDescription(i18n("A label to display text")); + addClass(wLabel); + + KFormDesigner::WidgetInfo *wPicLabel = new KFormDesigner::WidgetInfo(this); + wPicLabel->setPixmap("pixmaplabel"); + wPicLabel->setClassName("PicLabel"); + wPicLabel->setName(i18n("Picture Label")); + wPicLabel->setNamePrefix( + i18n("Widget name. This string will be used to name widgets of this class. It must _not_ contain white spaces and non latin1 characters.", "picture")); + wPicLabel->setDescription(i18n("A label to display images or icons")); + addClass(wPicLabel); + + KFormDesigner::WidgetInfo *wLine = new KFormDesigner::WidgetInfo(this); + wLine->setPixmap("line"); + wLine->setClassName("ReportLine"); + wLine->setName(i18n("Line")); + wLine->setNamePrefix( + i18n("Widget name. This string will be used to name widgets of this class. It must _not_ contain white spaces and non latin1 characters.", "line")); + wLine->setDescription(i18n("A simple line")); + addClass(wLine); + + KFormDesigner::WidgetInfo *wSubReport = new KFormDesigner::WidgetInfo(this); + wSubReport->setPixmap("report"); + wSubReport->setClassName("KexiSubReport"); + wSubReport->setName(i18n("Sub Report")); + wSubReport->setNamePrefix( + i18n("Widget name. This string will be used to name widgets of this class. It must _not_ contain white spaces and non latin1 characters.", "subReport")); + wSubReport->setDescription(i18n("A report embedded in another report")); + addClass(wSubReport); +} + +KexiReportFactory::~KexiReportFactory() +{ +} + +QString +KexiReportFactory::name() +{ + return "kexireportwidgets"; +} + +QWidget* +KexiReportFactory::createWidget(const QCString &c, QWidget *p, const char *n, + KFormDesigner::Container *container, int options) +{ + Q_UNUSED(options); + kexipluginsdbg << "KexiReportFactory::create() " << this << endl; + + QString text( container->form()->library()->textForWidgetName(n, c) ); + + if(c == "Label") + return new Label(text, p, n); + else if(c == "PicLabel") + return new PicLabel(DesktopIcon("image"), p, n); + else if(c == "ReportLine") + return new ReportLine(p, n); + else if(c == "KexiSubReport") + return new KexiSubReport(p, n); + + return 0; +} + +bool +KexiReportFactory::createMenuActions(const QCString &classname, QWidget *w, + QPopupMenu *menu, KFormDesigner::Container *container) +{ + Q_UNUSED(w); + Q_UNUSED(container); + if(classname == "Label") { + /*! @todo use KAction */ + menu->insertItem(SmallIconSet("edit"), i18n("Edit Rich Text"), this, SLOT(editText())); + return true; + } + return false; +} + +bool +KexiReportFactory::startEditing(const QCString &c, QWidget *w, KFormDesigner::Container *container) +{ + m_container = container; + + if(c == "Label") { + QLabel *label = static_cast<QLabel*>(w); + if(label->textFormat() == RichText) { + m_widget = w; + editText(); + } + else + createEditor(c, label->text(), label, container, label->geometry(), label->alignment()); + return true; + } + return false; +} + +bool +KexiReportFactory::isPropertyVisibleInternal(const QCString &classname, QWidget *w, const QCString &property, bool isTopLevel) +{ + if(classname == "Label") { + if(property == "pixmap") + return false; + } + else if(classname == "PicLabel") { + if((property == "text") || (property == "indent") || (property == "textFormat") || (property == "font") || (property == "alignment")) + return false; + } + + return WidgetFactory::isPropertyVisibleInternal(classname, w, property, isTopLevel); +} + +QValueList<QCString> +KexiReportFactory::autoSaveProperties(const QCString &classname) +{ + QValueList<QCString> l; + + if(classname == "Label") + l << "text"; + else if(classname == "PicLabel") + l << "pixmap"; + + return l; +} + +/* +void +KexiReportFactory::changeText(const QString &text) +{ + QWidget *w = WidgetFactory::m_widget; + changeProperty("text", text, m_container); + + int width = w->sizeHint().width(); + + if(w->width() < width) + w->resize(width, w->height() ); +} + +void +KexiReportFactory::resizeEditor(QWidget *widget, const QCString &) +{ + QSize s = widget->size(); + QPoint p = widget->pos(); + QRect r; + + m_editor->resize(s); + m_editor->move(p); +}*/ + +void +KexiReportFactory::editText() +{ + QCString classname = m_widget->className(); + QString text; + + if(classname == "Label") + text = ((QLabel*)m_widget)->text(); + + if(editRichText(m_widget, text)) { + changeProperty("textFormat", "RichText", m_container->form()); + changeProperty("text", text, m_container->form()); + } + + if(classname == "Label") + m_widget->resize(m_widget->sizeHint()); +} + +bool +KexiReportFactory::previewWidget(const QCString &, QWidget *, KFormDesigner::Container *) +{ + return false; +} + +KFORMDESIGNER_WIDGET_FACTORY(KexiReportFactory, kexireportwidgets) + +#include "kexireportfactory.moc" + diff --git a/kexi/plugins/reports/kexireportfactory.h b/kexi/plugins/reports/kexireportfactory.h new file mode 100644 index 00000000..c6b91702 --- /dev/null +++ b/kexi/plugins/reports/kexireportfactory.h @@ -0,0 +1,62 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr> + + 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 KEXIREPORTFACTORY_H +#define KEXIREPORTFACTORY_H + +#include <widgetfactory.h> + +//! Kexi Factory (DB widgets + subform) +class KexiReportFactory : public KFormDesigner::WidgetFactory +{ + Q_OBJECT + + public: + KexiReportFactory(QObject *parent, const char *name, const QStringList &args); + virtual ~KexiReportFactory(); + + virtual QString name(); + virtual QWidget *createWidget(const QCString &classname, QWidget *parent, const char *name, KFormDesigner::Container *container, + int options = DefaultOptions); + + virtual bool createMenuActions(const QCString &classname, QWidget *w, QPopupMenu *menu, + KFormDesigner::Container *container); + virtual bool startEditing(const QCString &classname, QWidget *w, KFormDesigner::Container *container); + virtual bool previewWidget(const QCString &, QWidget *, KFormDesigner::Container *); + + //virtual void saveSpecialProperty(const QString &classname, const QString &name, const QVariant &value, QWidget *w, + //QDomElement &parentNode, QDomDocument &parent) {} + //virtual void readSpecialProperty(const QCString &classname, QDomElement &node, QWidget *w, KFormDesigner::ObjectTreeItem *item) {} + virtual QValueList<QCString> autoSaveProperties(const QCString &classname); + + public slots: + void editText(); + + protected: + virtual bool isPropertyVisibleInternal(const QCString &, QWidget *, const QCString &, bool isTopLevel); +// virtual void changeText(const QString &newText); +// virtual void resizeEditor(QWidget *widget, const QCString &classname); + + private: + QWidget *m_widget; + KFormDesigner::Container *m_container; +}; + +#endif + diff --git a/kexi/plugins/reports/kexireportform.cpp b/kexi/plugins/reports/kexireportform.cpp new file mode 100644 index 00000000..d5dd6f55 --- /dev/null +++ b/kexi/plugins/reports/kexireportform.cpp @@ -0,0 +1,188 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Lucijan Busch <lucijan@kde.org> + Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr> + + 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 <qobjectlist.h> +#include <qpainter.h> +#include <qcursor.h> + +#include <kdebug.h> + +#include "kexireportform.h" + +KexiReportForm::KexiReportForm(QWidget *parent, const char *name/*, KexiDB::Connection *conn*/) + : QWidget(parent, name) +{ + //m_conn = conn; + kexipluginsdbg << "KexiReportForm::KexiReportForm(): " << endl; + setCursor(QCursor(Qt::ArrowCursor)); //to avoid keeping Size cursor when moving from form's boundaries + setBackgroundColor(white); +} + +KexiReportForm::~KexiReportForm() +{ + kexipluginsdbg << "KexiReportForm::~KexiReportForm(): close" << endl; +} + +//repaint all children widgets +static void repaintAll(QWidget *w) +{ + QObjectList *list = w->queryList("QWidget"); + QObjectListIt it(*list); + for (QObject *obj; (obj=it.current()); ++it ) { + static_cast<QWidget*>(obj)->repaint(); + } + delete list; +} + +void +KexiReportForm::drawRect(const QRect& r, int type) +{ + QValueList<QRect> l; + l.append(r); + drawRects(l, type); +} + +void +KexiReportForm::drawRects(const QValueList<QRect> &list, int type) +{ + QPainter p; + p.begin(this, true); + bool unclipped = testWFlags( WPaintUnclipped ); + setWFlags( WPaintUnclipped ); + + if (prev_rect.isValid()) { + //redraw prev. selection's rectangle + p.drawPixmap( QPoint(prev_rect.x()-2, prev_rect.y()-2), buffer, QRect(prev_rect.x()-2, prev_rect.y()-2, prev_rect.width()+4, prev_rect.height()+4)); + } + p.setBrush(QBrush::NoBrush); + if(type == 1) // selection rect + p.setPen(QPen(white, 1, Qt::DotLine)); + else if(type == 2) // insert rect + p.setPen(QPen(white, 2)); + p.setRasterOp(XorROP); + + prev_rect = QRect(); + QValueList<QRect>::ConstIterator endIt = list.constEnd(); + for(QValueList<QRect>::ConstIterator it = list.constBegin(); it != endIt; ++it) { + p.drawRect(*it); + prev_rect = prev_rect.unite(*it); + } + + if (!unclipped) + clearWFlags( WPaintUnclipped ); + p.end(); +} + +void +KexiReportForm::initBuffer() +{ + repaintAll(this); + buffer.resize( width(), height() ); + buffer = QPixmap::grabWindow( winId() ); + prev_rect = QRect(); +} + +void +KexiReportForm::clearForm() +{ + QPainter p; + p.begin(this, true); + bool unclipped = testWFlags( WPaintUnclipped ); + setWFlags( WPaintUnclipped ); + + //redraw entire form surface + p.drawPixmap( QPoint(0,0), buffer, QRect(0,0,buffer.width(), buffer.height()) ); + + if (!unclipped) + clearWFlags( WPaintUnclipped ); + p.end(); + + repaintAll(this); +} + +void +KexiReportForm::highlightWidgets(QWidget *from, QWidget *to)//, const QPoint &point) +{ + QPoint fromPoint, toPoint; + if(from && from->parentWidget() && (from != this)) + fromPoint = from->parentWidget()->mapTo(this, from->pos()); + if(to && to->parentWidget() && (to != this)) + toPoint = to->parentWidget()->mapTo(this, to->pos()); + + QPainter p; + p.begin(this, true); + bool unclipped = testWFlags( WPaintUnclipped ); + setWFlags( WPaintUnclipped ); + + if (prev_rect.isValid()) { + //redraw prev. selection's rectangle + p.drawPixmap( QPoint(prev_rect.x(), prev_rect.y()), buffer, QRect(prev_rect.x(), prev_rect.y(), prev_rect.width(), prev_rect.height())); + } + + p.setPen( QPen(Qt::red, 2) ); + + if(to) + { + QPixmap pix1 = QPixmap::grabWidget(from); + QPixmap pix2 = QPixmap::grabWidget(to); + + if((from != this) && (to != this)) + p.drawLine( from->parentWidget()->mapTo(this, from->geometry().center()), to->parentWidget()->mapTo(this, to->geometry().center()) ); + + p.drawPixmap(fromPoint.x(), fromPoint.y(), pix1); + p.drawPixmap(toPoint.x(), toPoint.y(), pix2); + + if(to == this) + p.drawRoundRect(2, 2, width()-4, height()-4, 4, 4); + else + p.drawRoundRect(toPoint.x(), toPoint.y(), to->width(), to->height(), 5, 5); + } + + if(from == this) + p.drawRoundRect(2, 2, width()-4, height()-4, 4, 4); + else + p.drawRoundRect(fromPoint.x(), fromPoint.y(), from->width(), from->height(), 5, 5); + + if((to == this) || (from == this)) + prev_rect = QRect(0, 0, buffer.width(), buffer.height()); + else if(to) + { + prev_rect.setX( (fromPoint.x() < toPoint.x()) ? (fromPoint.x() - 5) : (toPoint.x() - 5) ); + prev_rect.setY( (fromPoint.y() < toPoint.y()) ? (fromPoint.y() - 5) : (toPoint.y() - 5) ); + prev_rect.setRight( (fromPoint.x() < toPoint.x()) ? (toPoint.x() + to->width() + 10) : (fromPoint.x() + from->width() + 10) ); + prev_rect.setBottom( (fromPoint.y() < toPoint.y()) ? (toPoint.y() + to->height() + 10) : (fromPoint.y() + from->height() + 10) ) ; + } + else + prev_rect = QRect(fromPoint.x()- 5, fromPoint.y() -5, from->width() + 10, from->height() + 10); + + if (!unclipped) + clearWFlags( WPaintUnclipped ); + p.end(); +} + +QSize +KexiReportForm::sizeHint() const +{ + //todo: find better size (user configured?) + return QSize(400,300); +} + +#include "kexireportform.moc" + diff --git a/kexi/plugins/reports/kexireportform.h b/kexi/plugins/reports/kexireportform.h new file mode 100644 index 00000000..8b03c2ec --- /dev/null +++ b/kexi/plugins/reports/kexireportform.h @@ -0,0 +1,60 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Lucijan Busch <lucijan@kde.org> + Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr> + + 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 KEXIREPORTFORM_H +#define KEXIREPORTFORM_H + +#include <qwidget.h> +#include <qpixmap.h> + +#include <formeditor/form.h> + +//! The report top widget +class KEXIREPORTUTILS_EXPORT KexiReportForm : public QWidget, public KFormDesigner::FormWidget +{ + Q_OBJECT + + public: + KexiReportForm(QWidget *parent, const char *name="kexi_dbform"); + virtual ~KexiReportForm(); + + /*QString datasource() const { return m_ds; } + bool navigatorShown() const { return m_nav; } + void setDatasource(const QString &s) { m_ds = s; } + void showRecordNavigator(bool s) { m_nav = s; }*/ + + virtual void drawRect(const QRect& r, int type); + virtual void drawRects(const QValueList<QRect> &list, int type); + virtual void initBuffer(); + virtual void clearForm(); + virtual void highlightWidgets(QWidget *from, QWidget *to/*, const QPoint &p*/); + + virtual QSize sizeHint() const; + + private: + /*QString m_ds; + bool m_nav; + KexiDB::Connection *m_conn;*/ + + QPixmap buffer; //!< stores grabbed entire form's area for redraw + QRect prev_rect; //!< previously selected rectangle +}; + +#endif diff --git a/kexi/plugins/reports/kexireporthandler.desktop b/kexi/plugins/reports/kexireporthandler.desktop new file mode 100644 index 00000000..e9606dbf --- /dev/null +++ b/kexi/plugins/reports/kexireporthandler.desktop @@ -0,0 +1,108 @@ +[Desktop Entry] +Type=Service +ServiceTypes=Kexi/Handler + +GenericName=Reports +GenericName[bg]=Отчети +GenericName[ca]=Informes +GenericName[cs]=Sestavy +GenericName[cy]=Adroddiadau +GenericName[da]=Rapporter +GenericName[de]=Berichte +GenericName[el]=Αναφορές +GenericName[eo]=Raportoj +GenericName[es]=Informes +GenericName[et]=Aruanded +GenericName[eu]=Txostenak +GenericName[fa]=گزارشها +GenericName[fi]=Raportit +GenericName[fr]=Rapports +GenericName[fy]=Rapporten +GenericName[ga]=Tuairiscí +GenericName[gl]=Informes +GenericName[he]=דו"חות +GenericName[hr]=Izvještaji +GenericName[hu]=Jelentések +GenericName[is]=Skýrslur +GenericName[it]=Rapporti +GenericName[ja]=レポート +GenericName[km]=របាយការណ៍ +GenericName[lt]=Ataskaitos +GenericName[lv]=Atskaites +GenericName[ms]=Laporan +GenericName[nb]=Rapporter +GenericName[nds]=Berichten +GenericName[ne]=प्रतिवेदनहरू +GenericName[nl]=Rapporten +GenericName[nn]=Rapportar +GenericName[pl]=Raporty +GenericName[pt]=Relatórios +GenericName[pt_BR]=Relatórios +GenericName[ru]=Отчёты +GenericName[se]=Raporttat +GenericName[sk]=Správy +GenericName[sl]=Poročila +GenericName[sr]=Извештаји +GenericName[sr@Latn]=Izveštaji +GenericName[sv]=Rapporter +GenericName[uk]=Звіти +GenericName[uz]=Hisobotlar +GenericName[uz@cyrillic]=Ҳисоботлар +GenericName[zh_CN]=报表 +GenericName[zh_TW]=報告 +Name=Reports +Name[bg]=Отчети +Name[ca]=Informes +Name[cs]=Sestavy +Name[cy]=Adroddiadau +Name[da]=Rapporter +Name[de]=Berichte +Name[el]=Αναφορές +Name[eo]=Raportoj +Name[es]=Informes +Name[et]=Aruanded +Name[eu]=Txostenak +Name[fa]=گزارشها +Name[fi]=Raportit +Name[fr]=Rapports +Name[fy]=Rapporten +Name[ga]=Tuairiscí +Name[gl]=Informes +Name[he]=דו"חות +Name[hr]=Izvještaji +Name[hu]=Jelentések +Name[is]=Skýrslur +Name[it]=Rapporti +Name[ja]=レポート +Name[km]=របាយការណ៍ +Name[lt]=Ataskaitos +Name[lv]=Atskaites +Name[ms]=Laporan +Name[nb]=Rapporter +Name[nds]=Berichten +Name[ne]=प्रतिवेदनहरू +Name[nl]=Rapporten +Name[nn]=Rapportar +Name[pl]=Raporty +Name[pt]=Relatórios +Name[pt_BR]=Relatórios +Name[ru]=Отчёты +Name[se]=Raporttat +Name[sk]=Správy +Name[sl]=Poročila +Name[sr]=Извештаји +Name[sr@Latn]=Izveštaji +Name[sv]=Rapporter +Name[uk]=Звіти +Name[uz]=Hisobotlar +Name[uz@cyrillic]=Ҳисоботлар +Name[zh_CN]=报表 +Name[zh_TW]=報告 +X-KDE-Library=kexihandler_report +X-KDE-ParentApp=kexi +X-Kexi-PartVersion=2 +X-Kexi-TypeName=report +X-Kexi-TypeMime=kexi/report +X-Kexi-ItemIcon=report +X-Kexi-SupportsDataExport=false +X-Kexi-SupportsPrinting=false diff --git a/kexi/plugins/reports/kexireportpart.cpp b/kexi/plugins/reports/kexireportpart.cpp new file mode 100644 index 00000000..ad83cbf4 --- /dev/null +++ b/kexi/plugins/reports/kexireportpart.cpp @@ -0,0 +1,141 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Lucijan Busch <lucijan@kde.org> + Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr> + + 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 <kdebug.h> +#include <kgenericfactory.h> + +#include "kexiviewbase.h" +#include "keximainwindow.h" +#include "kexiproject.h" +#include <kexipartitem.h> +#include <kexidialogbase.h> + +#include <kexidb/connection.h> +#include <kexidb/fieldlist.h> +#include <kexidb/field.h> + +#include <form.h> +#include <formIO.h> +#include <widgetlibrary.h> + +#include <kexiformmanager.h> +#include <kexiformpart.h> + +#include "kexireportview.h" +#include "kexireportpart.h" + +KFormDesigner::WidgetLibrary* KexiReportPart::static_reportsLibrary = 0L; + +KexiReportPart::KexiReportPart(QObject *parent, const char *name, const QStringList &l) + : KexiPart::Part(parent, name, l) +{ + // REGISTERED ID: + m_registeredPartID = (int)KexiPart::ReportObjectType; + + kexipluginsdbg << "KexiReportPart::KexiReportPart()" << endl; + 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.", + "report"); + m_names["instanceCaption"] = i18n("Report"); + m_supportedViewModes = Kexi::DataViewMode | Kexi::DesignViewMode; + + // Only create form manager if it's not yet created. + // KexiFormPart could have created is already. + KFormDesigner::FormManager *formManager = KFormDesigner::FormManager::self(); + if (!formManager) + formManager = new KexiFormManager(this, "kexi_form_and_report_manager"); + + // Create and store a handle to report' library. Forms will have their own library too. +/* @todo add configuration for supported factory groups */ + QStringList supportedFactoryGroups; + supportedFactoryGroups += "kexi-report"; + static_reportsLibrary = KFormDesigner::FormManager::createWidgetLibrary( + formManager, supportedFactoryGroups); + static_reportsLibrary->setAdvancedPropertiesVisible(false); +} + +KexiReportPart::~KexiReportPart() +{ +} + +KFormDesigner::WidgetLibrary* KexiReportPart::library() +{ + return static_reportsLibrary; +} + +void +KexiReportPart::initPartActions() +{ +} + +void +KexiReportPart::initInstanceActions() +{ + KFormDesigner::FormManager::self()->createActions( + library(), actionCollectionForMode(Kexi::DesignViewMode), guiClient()); +} + +KexiDialogTempData* +KexiReportPart::createTempData(KexiDialogBase* dialog) +{ + return new KexiReportPart::TempData(dialog); +} + +KexiViewBase* +KexiReportPart::createView(QWidget *parent, KexiDialogBase* dialog, + KexiPart::Item &item, int, QMap<QString,QString>*) +{ + kexipluginsdbg << "KexiReportPart::createView()" << endl; + KexiMainWindow *win = dialog->mainWin(); + if (!win || !win->project() || !win->project()->dbConnection()) + return 0; + + KexiReportView *view = new KexiReportView(win, parent, item.name().latin1(), + win->project()->dbConnection() ); + + return view; +} + +QString +KexiReportPart::i18nMessage(const QCString& englishMessage, KexiDialogBase* dlg) const +{ + Q_UNUSED(dlg); + if (englishMessage=="Design of object \"%1\" has been modified.") + return i18n("Design of report \"%1\" has been modified."); + if (englishMessage=="Object \"%1\" already exists.") + return i18n("Report \"%1\" already exists."); + return englishMessage; +} + +//--------------- + +KexiReportPart::TempData::TempData(QObject* parent) + : KexiDialogTempData(parent) +{ +} + +KexiReportPart::TempData::~TempData() +{ +} + +#include "kexireportpart.moc" + diff --git a/kexi/plugins/reports/kexireportpart.h b/kexi/plugins/reports/kexireportpart.h new file mode 100644 index 00000000..19731e57 --- /dev/null +++ b/kexi/plugins/reports/kexireportpart.h @@ -0,0 +1,88 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Lucijan Busch <lucijan@kde.org> + Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr> + + 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 KEXIREPORTPART_H +#define KEXIREPORTPART_H + +#include <kexi.h> +#include <kexipart.h> +#include <kexidialogbase.h> + +namespace KFormDesigner +{ + class FormManager; + class WidgetLibrary; + class Form; +} + +namespace KexiDB +{ + class FieldList; +} + +/*! @short Kexi Report Plugin + It just creates a \ref KexiReportView. See there for most of code. */ +class KEXIREPORTUTILS_EXPORT KexiReportPart : public KexiPart::Part +{ + Q_OBJECT + + public: + KexiReportPart(QObject *parent, const char *name, const QStringList &); + virtual ~KexiReportPart(); + + //! \return a pointer to Reports Widget Library. + static KFormDesigner::WidgetLibrary* library(); + +// KFormDesigner::FormManager *manager() { return m_manager; } + + void generateForm(KexiDB::FieldList *list, QDomDocument &domDoc); + + class TempData : public KexiDialogTempData + { + public: + TempData(QObject* parent); + ~TempData(); + QGuardedPtr<KFormDesigner::Form> form; + QGuardedPtr<KFormDesigner::Form> previewForm; + QString tempForm; + QPoint scrollViewContentsPos; //!< to preserve contents pos after switching to other view + int resizeMode; //!< form's window's resize mode -one of KexiFormView::ResizeMode items + }; + + virtual QString i18nMessage(const QCString& englishMessage, + KexiDialogBase* dlg) const; + + 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(); + virtual void initInstanceActions(); + + static KFormDesigner::WidgetLibrary* static_reportsLibrary; + + private: +// QGuardedPtr<KFormDesigner::FormManager> m_manager; +}; + +#endif + diff --git a/kexi/plugins/reports/kexireportpartinstui.rc b/kexi/plugins/reports/kexireportpartinstui.rc new file mode 100644 index 00000000..9bfc8fe3 --- /dev/null +++ b/kexi/plugins/reports/kexireportpartinstui.rc @@ -0,0 +1,37 @@ +<!DOCTYPE kpartgui> +<kpartgui name="kexireportpartinst" version="1"> + +<MenuBar> + <Menu name="edit"> + <Action name="fompart_clear_contents"/> + </Menu> + <Menu name="format" noMerge="1"> + <text>&Format</text> + <Action name="snap_to_grid"/> + <Separator/> + <Action name="reportpart_align_menu"/> + <Action name="reportpart_adjust_size_menu"/> + <Separator/> + <Action name="reportpart_format_raise"/> + <Action name="reportpart_format_lower"/> + </Menu> +</MenuBar> + +<ToolBar name="widgets" fullWidth="false"> + <text>Widgets</text> + <Action name="pointer"/> + <Separator/> + <Action name="library_widget_KexiSubReport"/> + <Separator/> + <Action name="library_widget_Label"/> + <Action name="library_widget_PicLabel"/> + <Action name="library_widget_ReportLine"/> +</ToolBar> +<ToolBar name="format" fullWidth="false" noMerge="1"> +<text>Format Toolbar</text> + <Action name="reportpart_align_menu"/> + <Action name="reportpart_adjust_size_menu"/> +</ToolBar> + +</kpartgui> + diff --git a/kexi/plugins/reports/kexireportpartui.rc b/kexi/plugins/reports/kexireportpartui.rc new file mode 100644 index 00000000..a81b09b1 --- /dev/null +++ b/kexi/plugins/reports/kexireportpartui.rc @@ -0,0 +1,6 @@ +<!DOCTYPE kpartgui> +<kpartgui name="kexireportpart" version="1"> + + +</kpartgui> + diff --git a/kexi/plugins/reports/kexireports.cpp b/kexi/plugins/reports/kexireports.cpp new file mode 100644 index 00000000..51b03054 --- /dev/null +++ b/kexi/plugins/reports/kexireports.cpp @@ -0,0 +1,24 @@ +/* This file is part of the KDE project + Copyright (C) 2005 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 <kgenericfactory.h> + +#include "kexireportpart.h" + +K_EXPORT_COMPONENT_FACTORY( kexihandler_report, KGenericFactory<KexiReportPart>("kexihandler_report") ) diff --git a/kexi/plugins/reports/kexireportview.cpp b/kexi/plugins/reports/kexireportview.cpp new file mode 100644 index 00000000..6b7f46b5 --- /dev/null +++ b/kexi/plugins/reports/kexireportview.cpp @@ -0,0 +1,477 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr> + Copyright (C) 2004 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 "kexireportview.h" + +#include <kdebug.h> + +#include <form.h> +#include <formIO.h> +#include <formmanager.h> +#include <objecttree.h> +#include <widgetpropertyset.h> +#include <container.h> + +#include <kexidialogbase.h> +//#include <kexidatasourcewizard.h> +#include <kexidb/fieldlist.h> +#include <kexidb/connection.h> + +#include "kexireportform.h" +#include <utils/kexirecordnavigator.h> + +#define NO_DSWIZARD + +KexiReportScrollView::KexiReportScrollView(QWidget *parent, bool preview) + : KexiScrollView(parent, preview) +{ + if(preview) { + setRecordNavigatorVisible(true); + recordNavigator()->setLabelText(i18n("Page:")); + recordNavigator()->setInsertingButtonVisible(false); + } + connect(this, SIGNAL(resizingStarted()), this, SLOT(slotResizingStarted())); +} + +KexiReportScrollView::~KexiReportScrollView() +{ +} + +void +KexiReportScrollView::show() +{ + KexiScrollView::show(); + + //now get resize mode settings for entire form + if (m_preview) { + KexiReportView* fv = dynamic_cast<KexiReportView*>(parent()); + int resizeMode = fv ? fv->resizeMode() : KexiReportView::ResizeAuto; + if (resizeMode == KexiReportView::ResizeAuto) + setResizePolicy(AutoOneFit); + } +} + +void +KexiReportScrollView::slotResizingStarted() +{ + if(m_form && KFormDesigner::FormManager::self()) + setSnapToGrid(KFormDesigner::FormManager::self()->snapWidgetsToGrid(), m_form->gridSize()); + else + setSnapToGrid(false); +} + +////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////// + +KexiReportView::KexiReportView(KexiMainWindow *win, QWidget *parent, const char *name, + KexiDB::Connection *conn) + : KexiViewBase(win, parent, name), m_propertySet(0), m_conn(conn) + , m_resizeMode(KexiReportView::ResizeDefault) +{ + QHBoxLayout *l = new QHBoxLayout(this); + l->setAutoAdd(true); + + m_scrollView = new KexiReportScrollView(this, viewMode()==Kexi::DataViewMode); + setViewWidget(m_scrollView); +// m_scrollView->show(); + + m_reportform = new KexiReportForm(m_scrollView->viewport(), name/*, conn*/); +// m_reportform->resize(QSize(400, 300)); + m_scrollView->setWidget(m_reportform); + m_scrollView->setResizingEnabled(viewMode()!=Kexi::DataViewMode); + +// initForm(); + + if (viewMode()==Kexi::DataViewMode) { + m_scrollView->viewport()->setPaletteBackgroundColor(m_reportform->palette().active().background()); +#if 0 + connect(reportPart()->manager(), SIGNAL(noFormSelected()), SLOT(slotNoFormSelected())); +#endif + } + else { + connect(KFormDesigner::FormManager::self(), SIGNAL(propertySetSwitched(KoProperty::Set *, bool)), + this, SLOT(slotPropertySetSwitched(KoProperty::Set *, bool))); + connect(KFormDesigner::FormManager::self(), SIGNAL(dirty(KFormDesigner::Form *, bool)), + this, SLOT(slotDirty(KFormDesigner::Form *, bool))); + + // action stuff + /*connect(reportPart()->manager(), SIGNAL(widgetSelected(KFormDesigner::Form*, bool)), SLOT(slotWidgetSelected(KFormDesigner::Form*, bool))); + connect(reportPart()->manager(), SIGNAL(formWidgetSelected(KFormDesigner::Form*)), SLOT(slotFormWidgetSelected(KFormDesigner::Form*))); + connect(reportPart()->manager(), SIGNAL(undoEnabled(bool, const QString&)), this, SLOT(setUndoEnabled(bool))); + connect(reportPart()->manager(), SIGNAL(redoEnabled(bool, const QString&)), this, SLOT(setRedoEnabled(bool)));*/ + + plugSharedAction("edit_copy", KFormDesigner::FormManager::self(), SLOT(copyWidget())); + plugSharedAction("edit_cut", KFormDesigner::FormManager::self(), SLOT(cutWidget())); + plugSharedAction("edit_paste", KFormDesigner::FormManager::self(), SLOT(pasteWidget())); + plugSharedAction("edit_delete", KFormDesigner::FormManager::self(), SLOT(deleteWidget())); + plugSharedAction("edit_select_all", KFormDesigner::FormManager::self(), SLOT(selectAll())); + plugSharedAction("reportpart_clear_contents", KFormDesigner::FormManager::self(), SLOT(clearWidgetContent())); + plugSharedAction("edit_undo", KFormDesigner::FormManager::self(), SLOT(undo())); + plugSharedAction("edit_redo", KFormDesigner::FormManager::self(), SLOT(redo())); + + plugSharedAction("reportpart_format_raise", KFormDesigner::FormManager::self(), SLOT(bringWidgetToFront()) ); + plugSharedAction("reportpart_format_lower", KFormDesigner::FormManager::self(), SLOT(sendWidgetToBack()) ); + + plugSharedAction("reportpart_align_menu", KFormDesigner::FormManager::self(), 0 ); + plugSharedAction("reportpart_align_to_left", KFormDesigner::FormManager::self(),SLOT(alignWidgetsToLeft()) ); + plugSharedAction("reportpart_align_to_right", KFormDesigner::FormManager::self(), SLOT(alignWidgetsToRight()) ); + plugSharedAction("reportpart_align_to_top", KFormDesigner::FormManager::self(), SLOT(alignWidgetsToTop()) ); + plugSharedAction("reportpart_align_to_bottom", KFormDesigner::FormManager::self(), SLOT(alignWidgetsToBottom()) ); + plugSharedAction("reportpart_align_to_grid", KFormDesigner::FormManager::self(), SLOT(alignWidgetsToGrid()) ); + + plugSharedAction("reportpart_adjust_size_menu", KFormDesigner::FormManager::self(), 0 ); + plugSharedAction("reportpart_adjust_to_fit", KFormDesigner::FormManager::self(), SLOT(adjustWidgetSize()) ); + plugSharedAction("reportpart_adjust_size_grid", KFormDesigner::FormManager::self(), SLOT(adjustSizeToGrid()) ); + plugSharedAction("reportpart_adjust_height_small", KFormDesigner::FormManager::self(), SLOT(adjustHeightToSmall()) ); + plugSharedAction("reportpart_adjust_height_big", KFormDesigner::FormManager::self(), SLOT(adjustHeightToBig()) ); + plugSharedAction("reportpart_adjust_width_small", KFormDesigner::FormManager::self(), SLOT(adjustWidthToSmall()) ); + plugSharedAction("reportpart_adjust_width_big", KFormDesigner::FormManager::self(), SLOT(adjustWidthToBig()) ); + } + + initForm(); + + connect(this, SIGNAL(focus(bool)), this, SLOT(slotFocus(bool))); + /// @todo skip this if ther're no borders +// m_reportform->resize( m_reportform->size()+QSize(m_scrollView->verticalScrollBar()->width(), m_scrollView->horizontalScrollBar()->height()) ); +} + +KexiReportView::~KexiReportView() +{ + // Important: form window is closed. + // Set property set to 0 because there is *only one* instance of a property set class + // in Kexi, so the main window wouldn't know the set in fact has been changed. + m_propertySet = 0; + propertySetSwitched(); +} + +KFormDesigner::Form* +KexiReportView::form() const +{ + if(viewMode()==Kexi::DataViewMode) + return tempData()->previewForm; + else + return tempData()->form; +} + +void +KexiReportView::setForm(KFormDesigner::Form *f) +{ + if(viewMode()==Kexi::DataViewMode) + tempData()->previewForm = f; + else + tempData()->form = f; +} + +void +KexiReportView::initForm() +{ + setForm( new KFormDesigner::Form(KexiReportPart::library()) ); + form()->createToplevel(m_reportform, m_reportform); + + // Show the form wizard if this is a new Form +// KexiDB::FieldList *fields = 0; + if(parentDialog()->id() < 0) + { +#ifndef NO_DSWIZARD + KexiDataSourceWizard *w = new KexiDataSourceWizard(mainWin(), (QWidget*)mainWin(), "datasource_wizard"); + if(!w->exec()) + fields = 0; + else + fields = w->fields(); + delete w; +#endif + } + +/* if(fields) + { + @todo generate a report from a table or a query + QDomDocument dom; + reportPart()->generateForm(fields, dom); + KFormDesigner::FormIO::loadFormFromDom(form(), m_reportform, dom); + } + else*/ + loadForm(); + + KFormDesigner::FormManager::self()->importForm(form(), viewMode()==Kexi::DataViewMode); + m_scrollView->setForm(form()); + m_scrollView->refreshContentsSize(); +} + +void +KexiReportView::loadForm() +{ + +//@todo also load m_resizeMode ! + + kexipluginsdbg << "KexiReportForm::loadForm() Loading the form with id : " << parentDialog()->id() << endl; + // If we are previewing the Form, use the tempData instead of the form stored in the db + if(viewMode()==Kexi::DataViewMode && !tempData()->tempForm.isNull() ) { + KFormDesigner::FormIO::loadFormFromString(form(), m_reportform, tempData()->tempForm); + return; + } + + // normal load + QString data; + loadDataBlock(data); + KFormDesigner::FormIO::loadFormFromString(form(), m_reportform, data); +} + +void +KexiReportView::slotPropertySetSwitched(KoProperty::Set *set, bool forceReload) +{ + m_propertySet = set; + if (forceReload) + propertySetReloaded(true/*preservePrevSelection*/); + else + propertySetSwitched(); +} + +tristate +KexiReportView::beforeSwitchTo(int mode, bool &dontStore) +{ + if (mode!=viewMode() && viewMode()!=Kexi::DataViewMode) { + //remember our pos + tempData()->scrollViewContentsPos + = QPoint(m_scrollView->contentsX(), m_scrollView->contentsY()); + } + + // we don't store on db, but in our TempData + dontStore = true; + if(dirty() && (mode == Kexi::DataViewMode) && form()->objectTree()) + KFormDesigner::FormIO::saveFormToString(form(), tempData()->tempForm); + + return true; +} + +tristate +KexiReportView::afterSwitchFrom(int mode) +{ + if (mode != 0 && mode != Kexi::DesignViewMode) { + //preserve contents pos after switching to other view + m_scrollView->setContentsPos(tempData()->scrollViewContentsPos.x(), + tempData()->scrollViewContentsPos.y()); + } +// if (mode == Kexi::DesignViewMode) { + //m_scrollView->move(0,0); + //m_scrollView->setContentsPos(0,0); + //m_scrollView->moveChild(m_reportform, 0, 0); +// } + + if((mode == Kexi::DesignViewMode) && viewMode()==Kexi::DataViewMode) { + // The form may have been modified, so we must recreate the preview + delete m_reportform; // also deletes form() + m_reportform = new KexiReportForm(m_scrollView->viewport()); + m_scrollView->setWidget(m_reportform); + + initForm(); +#if 0 + slotNoFormSelected(); +#endif + + //reset position + m_scrollView->setContentsPos(0,0); + m_reportform->move(0,0); + } + return true; +} + +void +KexiReportView::slotDirty(KFormDesigner::Form *dirtyForm, bool isDirty) +{ + if(dirtyForm == form()) + KexiViewBase::setDirty(isDirty); +} + +KexiDB::SchemaData* +KexiReportView::storeNewData(const KexiDB::SchemaData& sdata, bool &cancel) +{ + KexiDB::SchemaData *s = KexiViewBase::storeNewData(sdata, cancel); + kexipluginsdbg << "KexiReportForm::storeNewData(): new id:" << s->id() << endl; + + if (!s || cancel) { + delete s; + return 0; + } + if (!storeData()) { + //failure: remove object's schema data to avoid garbage + m_conn->removeObject( s->id() ); + delete s; + return 0; + } + return s; +} + +tristate +KexiReportView::storeData(bool dontAsk) +{ + Q_UNUSED(dontAsk) + kexipluginsdbg << "KexiReportForm::storeData(): " << parentDialog()->partItem()->name() << " [" << parentDialog()->id() << "]" << endl; + QString data; + KFormDesigner::FormIO::saveFormToString(tempData()->form, data); + if (!storeDataBlock(data)) + return false; + tempData()->tempForm = QString(); + + return true; +} + +#if 0 +/// Action stuff ///////////////// +void +KexiReportView::slotWidgetSelected(KFormDesigner::Form *f, bool multiple) +{ + if(f != form()) + return; + + enableFormActions(); + // Enable edit actions + setAvailable("edit_copy", true); + setAvailable("edit_cut", true); + setAvailable("edit_clear", true); + + // 'Align Widgets' menu + setAvailable("reportpart_align_menu", multiple); + setAvailable("reportpart_align_to_left", multiple); + setAvailable("reportpart_align_to_right", multiple); + setAvailable("reportpart_align_to_top", multiple); + setAvailable("reportpart_align_to_bottom", multiple); + + setAvailable("reportpart_adjust_size_menu", true); + setAvailable("reportpart_adjust_width_small", multiple); + setAvailable("reportpart_adjust_width_big", multiple); + setAvailable("reportpart_adjust_height_small", multiple); + setAvailable("reportpart_adjust_height_big", multiple); + + setAvailable("reportpart_format_raise", true); + setAvailable("reportpart_format_lower", true); +} + +void +KexiReportView::slotFormWidgetSelected(KFormDesigner::Form *f) +{ + if(f != form()) + return; + + disableWidgetActions(); + enableFormActions(); +} + +void +KexiReportView::slotNoFormSelected() // == form in preview mode +{ + disableWidgetActions(); + + // Disable paste action + setAvailable("edit_paste", false); + setAvailable("edit_undo", false); + setAvailable("edit_redo", false); +} + +void +KexiReportView::enableFormActions() +{ + setAvailable("edit_paste", KFormDesigner::FormManager::self()->isPasteEnabled()); +} + +void +KexiReportView::disableWidgetActions() +{ + // Disable edit actions + setAvailable("edit_copy", false); + setAvailable("edit_cut", false); + setAvailable("edit_clear", false); + + // Disable format functions + setAvailable("reportpart_align_menu", false); + setAvailable("reportpart_align_to_left", false); + setAvailable("reportpart_align_to_right", false); + setAvailable("reportpart_align_to_top", false); + setAvailable("reportpart_align_to_bottom", false); + + setAvailable("reportpart_adjust_size_menu", false); + setAvailable("reportpart_adjust_width_small", false); + setAvailable("reportpart_adjust_width_big", false); + setAvailable("reportpart_adjust_height_small", false); + setAvailable("reportpart_adjust_height_big", false); + + setAvailable("reportpart_format_raise", false); + setAvailable("reportpart_format_lower", false); +} + +void +KexiReportView::setUndoEnabled(bool enabled) +{ + setAvailable("edit_undo", enabled); +} + +void +KexiReportView::setRedoEnabled(bool enabled) +{ + setAvailable("edit_redo", enabled); +} +#endif + +QSize +KexiReportView::preferredSizeHint(const QSize& otherSize) +{ + return (m_reportform->size() + +QSize(m_scrollView->verticalScrollBar()->isVisible() ? m_scrollView->verticalScrollBar()->width()*3/2 : 10, + m_scrollView->horizontalScrollBar()->isVisible() ? m_scrollView->horizontalScrollBar()->height()*3/2 : 10)) + .expandedTo( KexiViewBase::preferredSizeHint(otherSize) ); +} + +void +KexiReportView::resizeEvent( QResizeEvent *e ) +{ + if (viewMode()==Kexi::DataViewMode) { + m_scrollView->refreshContentsSizeLater( + e->size().width()!=e->oldSize().width(), + e->size().height()!=e->oldSize().height() + ); + } + KexiViewBase::resizeEvent(e); + m_scrollView->updateNavPanelGeometry(); +} + +void +KexiReportView::show() +{ + KexiViewBase::show(); + +//moved from KexiFormScrollView::show(): + + //now get resize mode settings for entire form + // if (resizeMode() == KexiFormView::ResizeAuto) + if (viewMode()==Kexi::DataViewMode) { + if (resizeMode() == ResizeAuto) + m_scrollView->setResizePolicy(QScrollView::AutoOneFit); + } +} + +void +KexiReportView::slotFocus(bool in) +{ + if(in && form() && KFormDesigner::FormManager::self() && KFormDesigner::FormManager::self()->activeForm() != form()) + KFormDesigner::FormManager::self()->windowChanged(form()->widget());//m_dbform); +} + + +#include "kexireportview.moc" + diff --git a/kexi/plugins/reports/kexireportview.h b/kexi/plugins/reports/kexireportview.h new file mode 100644 index 00000000..b600c06d --- /dev/null +++ b/kexi/plugins/reports/kexireportview.h @@ -0,0 +1,130 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr> + Copyright (C) 2004 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 KEXIREPORTVIEW_H +#define KEXIREPORTVIEW_H + +#include <qscrollview.h> +#include <qtimer.h> + +#include <kexiviewbase.h> + +#include "kexiscrollview.h" +#include "kexireportpart.h" + +class KexiReportForm; + +class KEXIREPORTUTILS_EXPORT KexiReportScrollView : public KexiScrollView +{ + Q_OBJECT + + public: + KexiReportScrollView(QWidget *parent, bool preview); + virtual ~KexiReportScrollView(); + + void setForm(KFormDesigner::Form *form) { m_form = form; } + + public slots: + /*! Reimplemented to update resize policy. */ + virtual void show(); + + protected slots: + void slotResizingStarted(); + + private: + KFormDesigner::Form *m_form; +}; + + +//! The FormPart's view +/*! This class presents a single view used inside KexiDialogBase. + It takes care of saving/loading report, of enabling actions when needed. + One KexiReportView object is instantiated for data view mode (preview == true in constructor), + and second KexiReportView object is instantiated for design view mode + (preview == false in constructor). */ +class KEXIREPORTUTILS_EXPORT KexiReportView : public KexiViewBase +{ + Q_OBJECT + + public: + enum ResizeMode { + ResizeAuto = 0, + ResizeDefault = ResizeAuto, + ResizeFixed = 1, + NoResize = 2 /*! @todo */ + }; + + KexiReportView(KexiMainWindow *win, QWidget *parent, const char *name, KexiDB::Connection *conn); + virtual ~KexiReportView(); + + KexiDB::Connection* connection() { return m_conn; } + + virtual QSize preferredSizeHint(const QSize& otherSize); + + int resizeMode() const { return m_resizeMode; } + + public slots: + /*! Reimplemented to update resize policy. */ + virtual void show(); + + protected slots: + void slotPropertySetSwitched(KoProperty::Set *set, bool forceReload = false); + void slotDirty(KFormDesigner::Form *f, bool isDirty); + void slotFocus(bool in); + + /*void slotWidgetSelected(KFormDesigner::Form *form, bool multiple); + void slotFormWidgetSelected(KFormDesigner::Form *form); + void slotNoFormSelected(); + + void setUndoEnabled(bool enabled); + void setRedoEnabled(bool enabled); */ + + protected: + virtual tristate beforeSwitchTo(int mode, bool &dontStore); + virtual tristate afterSwitchFrom(int mode); + virtual KoProperty::Set* propertySet() { return m_propertySet; } + + virtual KexiDB::SchemaData* storeNewData(const KexiDB::SchemaData& sdata, bool &cancel); + virtual tristate storeData(bool dontAsk = false); + + KexiReportPart::TempData* tempData() const { + return static_cast<KexiReportPart::TempData*>(parentDialog()->tempData()); } + KexiReportPart* reportPart() const { return static_cast<KexiReportPart*>(part()); } + + void disableWidgetActions(); + void enableFormActions(); + + KFormDesigner::Form* form() const; + void setForm(KFormDesigner::Form *f); + + void initForm(); + void loadForm(); + + virtual void resizeEvent ( QResizeEvent * ); + + private: + KexiReportForm *m_reportform; + KexiReportScrollView *m_scrollView; + KoProperty::Set *m_propertySet; + KexiDB::Connection *m_conn; + int m_resizeMode; +}; + +#endif diff --git a/kexi/plugins/reports/kformdesigner_kexireportfactory.desktop b/kexi/plugins/reports/kformdesigner_kexireportfactory.desktop new file mode 100644 index 00000000..46ccbd61 --- /dev/null +++ b/kexi/plugins/reports/kformdesigner_kexireportfactory.desktop @@ -0,0 +1,53 @@ +[Desktop Entry] +Type=Service +ServiceTypes=KFormDesigner/WidgetFactory + +Name=Kexi Report Widgets +Name[bg]=Графични елементи за отчети на Kexi +Name[ca]=Estris d'informe de Kexi +Name[cy]=Celfigion Adroddiad i Kexi +Name[da]=Kexi Rapportkontroller +Name[de]=Kexi Bericht-Elemente +Name[el]=Γραφικά συστατικά αναφορών Kexi +Name[eo]=Kexi-raportfenestraĵo +Name[es]=Widgets para informes de Kexi +Name[et]=Kexi aruandevidinad +Name[eu]=Kexi-ren txosten-trepetak +Name[fa]=عناصر گزارش Kexi +Name[fi]=Kexi Raporttielementit +Name[fr]=Éléments de rapport Kexi +Name[fy]=Kexi Rapport-widgets +Name[gl]=Elementos de Informe de Kexi +Name[he]=פריטי דו"חות של Kexi +Name[hr]=Kexi widgeti izvještaja +Name[hu]=Kexi jelentéskezelő grafikus elemek +Name[is]=Kexi skýrslu hlutar +Name[it]=Oggetti dei rapporti per Kexi +Name[ja]=Kexi レポートウィジェット +Name[km]=ធាតុក្រាហ្វិករបាយការណ៍សម្រាប់ Kexi +Name[lv]=Kexi atskaišu logdaļas +Name[ms]=Widget Laporan Kexi +Name[nb]=Skjermelement for Kexi-rapport +Name[nds]=Bericht-Elementen för Kexi +Name[ne]=केक्सी प्रतिवेदन विजेटहरू +Name[nl]=Kexi Rapportwidgets +Name[nn]=Skjermelement for Kexi-rapport +Name[pl]=Kontrolki raportów dla Kexi +Name[pt]=Elementos de Relatório do Kexi +Name[pt_BR]=Widgets de Relatório do Kexi +Name[ru]=Элементы управления для отчётов Kexi +Name[se]=Kexi-raportaáđat +Name[sk]=Moduly správ Kexi +Name[sl]=Gradniki za poročila za Kexi +Name[sr]=Kexi-јеве контроле за извештаје +Name[sr@Latn]=Kexi-jeve kontrole za izveštaje +Name[sv]=Kexi-rapportkomponenter +Name[uk]=Віджети звітів Kexi +Name[uz]=Kexi hisobot vidjetlari +Name[uz@cyrillic]=Kexi ҳисобот виджетлари +Name[zh_CN]=Kexi 报表部件 +Name[zh_TW]=Kexi 報告視窗元件 + +X-KDE-Library=kformdesigner_kexireportwidgets +X-KFormDesigner-FactoryGroup=kexi-report +X-KFormDesigner-WidgetFactoryVersion=2 diff --git a/kexi/plugins/reports/reportwidgets.cpp b/kexi/plugins/reports/reportwidgets.cpp new file mode 100644 index 00000000..5437325a --- /dev/null +++ b/kexi/plugins/reports/reportwidgets.cpp @@ -0,0 +1,181 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr> + + 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 <qpainter.h> + +#include <form.h> +#include <formIO.h> +#include <formmanager.h> +#include <kexidb/utils.h> +#include <kexidb/connection.h> +#include <kexipart.h> + +#include "kexireportview.h" +#include "reportwidgets.h" + +Label::Label(const QString &text, QWidget *parent, const char *name) +: QLabel(text, parent, name) +{ + setPaletteBackgroundColor(white); +} + +//////////////////////////////////////////////////////////////////// + +ReportLine::ReportLine(QWidget *parent, const char *name) +: QWidget(parent, name) +{ + m_lineStyle = (ReportLineStyle)Qt::SolidLine; + m_lineWidth = 1; + m_capStyle = (CapStyle)Qt::FlatCap; + m_color = paletteForegroundColor(); + setPaletteBackgroundColor(white); +} + +ReportLine::ReportLineStyle +ReportLine::lineStyle() const +{ + return m_lineStyle; +} + +void +ReportLine::setLineStyle(ReportLineStyle style) +{ + m_lineStyle = style; + update(); +} + +int +ReportLine::lineWidth() const +{ + return m_lineWidth; +} + +void +ReportLine::setLineWidth(int width) +{ + m_lineWidth = width; + update(); +} + +QColor +ReportLine::color() const +{ + return m_color; +} + +void +ReportLine::setColor(const QColor &color) +{ + m_color = color; + update(); +} + +ReportLine::CapStyle +ReportLine::capStyle() const +{ + return m_capStyle; +} + +void +ReportLine::setCapStyle(CapStyle capStyle) +{ + m_capStyle = capStyle; + update(); +} + +void +ReportLine::paintEvent (QPaintEvent *ev) +{ + QPainter p(this); + if(!ev->erased()) + p.eraseRect(0, 0, width(), height()); + QPen pen(m_color, m_lineWidth, (Qt::PenStyle)m_lineStyle); + pen.setCapStyle((Qt::PenCapStyle)m_capStyle); + p.setPen(pen); + p.drawLine(0, 0, width() -1, height() - 1); +} + +//////////////////////////////////////////////////////////////////// + + +PicLabel::PicLabel(const QPixmap &pix, QWidget *parent, const char *name) + : QLabel(parent, name) +{ + setPixmap(pix); + setScaledContents(false); + setPaletteBackgroundColor(white); +} + +bool +PicLabel::setProperty(const char *name, const QVariant &value) +{ + if(QString(name) == "pixmap") + resize(value.toPixmap().height(), value.toPixmap().width()); + return QLabel::setProperty(name, value); +} + +//////////////////////////////////////////////////////////////////// + +KexiSubReport::KexiSubReport(QWidget *parent, const char *name) +: QScrollView(parent, name), m_form(0), m_widget(0) +{ + setFrameStyle(QFrame::Plain | QFrame::Box); + viewport()->setPaletteBackgroundColor(white); +} + +void +KexiSubReport::setReportName(const QString &name) +{ + if(name.isEmpty()) + return; + + // we need a KexiReportView* + QWidget *w = parentWidget(); + while(w && !w->isA("KexiReportView")) + w = w->parentWidget(); + KexiReportView *view = (KexiReportView*)w; + if(!view) + return; + + // we check if there is a form with this name + int id = KexiDB::idForObjectName(*(view->connection()), name, KexiPart::ReportObjectType); + if((id == 0) || (id == view->parentDialog()->id())) // == our form + return; // because of recursion when loading + + // we create the container widget + delete m_widget; + m_widget = new QWidget(viewport(), "kexisubreport_widget"); + m_widget->show(); + addChild(m_widget); + m_form = new Form(KexiReportPart::library(), this->name()); + m_form->createToplevel(m_widget); + + // and load the sub form + QString data; + tristate res = view->connection()->loadDataBlock(id, data , QString::null); + if(res != true) + return; + + KFormDesigner::FormIO::loadFormFromString(m_form, m_widget, data); + m_form->setDesignMode(false); + + m_reportName = name; +} + +#include "reportwidgets.moc" + diff --git a/kexi/plugins/reports/reportwidgets.h b/kexi/plugins/reports/reportwidgets.h new file mode 100644 index 00000000..f8140b0c --- /dev/null +++ b/kexi/plugins/reports/reportwidgets.h @@ -0,0 +1,117 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr> + + 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 KEXIREPORTWIDGETS_H +#define KEXIREPORTWIDGETS_H + +#include <qlabel.h> +#include <qscrollview.h> + +namespace KFormDesigner { + class Form; + class FormManager; +} + +using KFormDesigner::Form; + +//! A form embedded as a widget inside other form +class KexiSubReport : public QScrollView +{ + Q_OBJECT + Q_PROPERTY(QString reportName READ reportName WRITE setReportName DESIGNABLE true); + + public: + KexiSubReport(QWidget *parent, const char *name); + ~KexiSubReport() {} + + //! \return the name of the subreport inside the db + QString reportName() const { return m_reportName; } + void setReportName(const QString &name); + + private: +// KFormDesigner::FormManager *m_manager; + Form *m_form; + QWidget *m_widget; + QString m_reportName; +}; + +//! A simple label inside a report +class Label : public QLabel +{ + Q_OBJECT + + public: + Label(const QString &text, QWidget *parent, const char *name); + ~Label() {} +}; + +//! A simple picture label inside a report +class PicLabel : public QLabel +{ + Q_OBJECT + + public: + PicLabel(const QPixmap &pix, QWidget *parent, const char *name); + ~PicLabel() {} + + virtual bool setProperty(const char *name, const QVariant &value); +}; + +//! A line +class ReportLine : public QWidget +{ + Q_OBJECT + Q_PROPERTY(ReportLineStyle lineStyle READ lineStyle WRITE setLineStyle) + Q_PROPERTY(int lineWidth READ lineWidth WRITE setLineWidth) + Q_PROPERTY(QColor color READ color WRITE setColor) + Q_PROPERTY(CapStyle capStyle READ capStyle WRITE setCapStyle) + + public: + enum ReportLineStyle { NoLine = Qt::NoPen, Solid = Qt::SolidLine, Dash = Qt::DashLine, Dot = Qt::DotLine, + DashDot = Qt::DashDotLine, DashDotDot = Qt::DashDotDotLine }; + enum CapStyle { Flat = Qt::FlatCap, Square = Qt::SquareCap, Round = Qt::RoundCap }; + + ReportLine(QWidget *parent, const char *name); + ~ReportLine(){;} + + ReportLineStyle lineStyle() const; + void setLineStyle(ReportLineStyle style); + + int lineWidth() const; + void setLineWidth(int width); + + QColor color() const; + void setColor(const QColor &color); + + CapStyle capStyle() const; + void setCapStyle(CapStyle capStyle); + + protected: + virtual void paintEvent (QPaintEvent *ev); + + private: + ReportLineStyle m_lineStyle; + int m_lineWidth; + CapStyle m_capStyle; + QColor m_color; +}; + + +#endif + diff --git a/kexi/plugins/scripting/Makefile.am b/kexi/plugins/scripting/Makefile.am new file mode 100644 index 00000000..ccd1bd64 --- /dev/null +++ b/kexi/plugins/scripting/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = kexiscripting kexiapp kexidb scripts diff --git a/kexi/plugins/scripting/README b/kexi/plugins/scripting/README new file mode 100644 index 00000000..e754b9cd --- /dev/null +++ b/kexi/plugins/scripting/README @@ -0,0 +1,28 @@ +Kexi Scripting README +--------------------- + +The code in this directory implements a scripting plugin for +Kexi. The Kross Scripting Framework located at koffice/libs/kross +is used to embed scripting interpreters and access Kexi +functionality from within those interpreters. + +See also http://www.kexi-project.org/wiki/wikiview/index.php?Scripting + +/kexiscripting/ +The scripting-plugin which will be loaded by Kexi at startup to +embed Kross into Kexi. + +/kexiapp/ +Access to a running Kexi application. Kexi itself takes care of +publishing it's KexiMainWindowImpl instance and the kexiapp-plugin +provides access to some of the applications functionality at runtime. + +/kexidb/ +Kross-plugin to provide nearly the whole KexiDB-framework to scripting +interpreters. That way we are able to read/write from/to all by KexiDB +supported databases. + +/scripts/ +Kexi-dependend scripts. This directory holds our in python or ruby +written scripting extensions. Those extensions are just plugins for +Kexi to extend it's functionality. diff --git a/kexi/plugins/scripting/kexiapp/Makefile.am b/kexi/plugins/scripting/kexiapp/Makefile.am new file mode 100644 index 00000000..a2702a26 --- /dev/null +++ b/kexi/plugins/scripting/kexiapp/Makefile.am @@ -0,0 +1,21 @@ +include $(top_srcdir)/kexi/Makefile.global + +INCLUDES = -I$(top_srcdir)/kexi $(KROSS_INCLUDES) $(all_includes) + +kde_module_LTLIBRARIES = krosskexiapp.la + +krosskexiapp_la_SOURCES = \ + kexiapppart.cpp \ + kexiappmainwindow.cpp \ + kexiappmodule.cpp + +krosskexiapp_la_LDFLAGS = $(all_libraries) $(KDE_PLUGIN) $(VER_INFO) -module +krosskexiapp_la_LIBADD = \ + $(LIB_QT) \ + $(LIB_KDECORE) \ + $(LIB_KROSS_API) \ + $(LIB_KROSS_MAIN) \ + $(top_builddir)/kexi/core/libkexicore.la + +METASOURCES = AUTO +SUBDIRS = . diff --git a/kexi/plugins/scripting/kexiapp/kexiappmainwindow.cpp b/kexi/plugins/scripting/kexiapp/kexiappmainwindow.cpp new file mode 100644 index 00000000..4d82bc5d --- /dev/null +++ b/kexi/plugins/scripting/kexiapp/kexiappmainwindow.cpp @@ -0,0 +1,106 @@ +/*************************************************************************** + * kexiappmainwindow.cpp + * This file is part of the KDE project + * copyright (C)2004-2005 by Sebastian Sauer (mail@dipe.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 "kexiappmainwindow.h" +#include "kexiapppart.h" + +#include "core/keximainwindow.h" +#include "core/kexiproject.h" +#include "core/kexi.h" +#include "kexidb/connection.h" + +#include "main/manager.h" + +//#include <kdebug.h> + +namespace Kross { namespace KexiApp { + + /// \internal + class KexiAppMainWindowPrivate + { + public: + KexiMainWindow* mainwindow; + + KexiProject* project() { + KexiProject* project = mainwindow->project(); + if(! project) + throw Kross::Api::Exception::Ptr( new Kross::Api::Exception("No project loaded.") ); + return project; + } + }; + +}} + +using namespace Kross::KexiApp; + +KexiAppMainWindow::KexiAppMainWindow(KexiMainWindow* mainwindow) + : Kross::Api::Class<KexiAppMainWindow>("KexiAppMainWindow") + , d(new KexiAppMainWindowPrivate()) +{ + d->mainwindow = mainwindow; + + this->addFunction0<Kross::Api::Variant>("isConnected", this, &KexiAppMainWindow::isConnected); + this->addFunction0<Kross::Api::Object>("getConnection", this, &KexiAppMainWindow::getConnection); + + this->addFunction1<Kross::Api::List, Kross::Api::Variant>("getPartItems", this, &KexiAppMainWindow::getPartItems); + this->addFunction1<Kross::Api::Variant, KexiAppPartItem>("openPartItem", this, &KexiAppMainWindow::openPartItem); +} + +KexiAppMainWindow::~KexiAppMainWindow() +{ + delete d; +} + +const QString KexiAppMainWindow::getClassName() const +{ + return "Kross::KexiApp::KexiAppMainWindow"; +} + +bool KexiAppMainWindow::isConnected() +{ + return d->project()->isConnected(); +} + +Kross::Api::Object::Ptr KexiAppMainWindow::getConnection() +{ + ::KexiDB::Connection* connection = d->project()->dbConnection(); + if(! connection) + throw Kross::Api::Exception::Ptr( new Kross::Api::Exception("No connection established.") ); + Kross::Api::Module::Ptr module = Kross::Api::Manager::scriptManager()->loadModule("krosskexidb"); + if(! module) + throw Kross::Api::Exception::Ptr( new Kross::Api::Exception("Could not load \"krosskexidb\" module.") ); + return module->get("KexiDBConnection", connection); +} + +Kross::Api::List* KexiAppMainWindow::getPartItems(const QString& mimetype) +{ + if(mimetype.isNull()) return 0; // just to be sure... + KexiPart::ItemDict* items = d->project()->itemsForMimeType( mimetype.latin1() ); + if(! items) return 0; + return new Kross::Api::ListT<KexiAppPartItem>( *items ); +} + +bool KexiAppMainWindow::openPartItem(KexiAppPartItem* partitem) +{ + bool openingCancelled; + KexiDialogBase* dialog = partitem + ? d->mainwindow->openObject(partitem->item(), Kexi::DataViewMode, openingCancelled) + : 0; + return (dialog != 0 && ! openingCancelled); +} diff --git a/kexi/plugins/scripting/kexiapp/kexiappmainwindow.h b/kexi/plugins/scripting/kexiapp/kexiappmainwindow.h new file mode 100644 index 00000000..fd02c193 --- /dev/null +++ b/kexi/plugins/scripting/kexiapp/kexiappmainwindow.h @@ -0,0 +1,91 @@ +/*************************************************************************** + * kexiappmainwindow.h + * This file is part of the KDE project + * copyright (C)2004-2005 by Sebastian Sauer (mail@dipe.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 KROSS_KEXIAPP_KEXIAPPMAINWINDOW_H +#define KROSS_KEXIAPP_KEXIAPPMAINWINDOW_H + +#include <qstring.h> +#include <qvariant.h> + +#include <api/object.h> +#include <api/variant.h> +#include <api/list.h> +#include <api/class.h> + +// Forward declarations. +class KexiMainWindow; + +namespace Kross { namespace KexiApp { + + // Forward declarations. + class KexiAppPartItem; + class KexiAppMainWindowPrivate; + + /** + * Class to handle Kexi's mainwindow instance. + */ + class KexiAppMainWindow : public Kross::Api::Class<KexiAppMainWindow> + { + public: + + /** + * Constructor. + * + * \param mainwindow The \a KexiMainWindow instance + * this class provides access to. + */ + KexiAppMainWindow(KexiMainWindow* mainwindow); + + /** + * Destructor. + */ + virtual ~KexiAppMainWindow(); + + /// \see Kross::Api::Object::getClassName + virtual const QString getClassName() const; + + /** \return true if Kexi is connected with a project else + false is returned. */ + bool isConnected(); + + /** \return the \a Kross::KexiDB::KexiDBConnection object that + belongs to the opened project or throw an exception if there + was no project opened (no connection established). Cause the + KexiApp-module doesn't know anything about the KexiDB-module + we have to use the base-class \a Kross::Api::Object to pass + the \a Kross::KexiDB::KexiDBConnection object around. */ + Kross::Api::Object::Ptr getConnection(); + + /** \return a list of \a KexiAppPartItem objects for the defined + \p mimetype string. */ + Kross::Api::List* getPartItems(const QString& mimetype); + + /** Try to open the defined \a KexiAppPartItem and \return true + on success else false. */ + bool openPartItem(KexiAppPartItem* partitem); + + private: + /// Private d-pointer class. + KexiAppMainWindowPrivate* d; + }; + +}} + +#endif + diff --git a/kexi/plugins/scripting/kexiapp/kexiappmodule.cpp b/kexi/plugins/scripting/kexiapp/kexiappmodule.cpp new file mode 100644 index 00000000..cb664496 --- /dev/null +++ b/kexi/plugins/scripting/kexiapp/kexiappmodule.cpp @@ -0,0 +1,97 @@ +/*************************************************************************** + * kexiappmodule.cpp + * This file is part of the KDE project + * copyright (C)2004-2005 by Sebastian Sauer (mail@dipe.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 "kexiappmodule.h" +#include "kexiappmainwindow.h" + +#include "core/keximainwindow.h" + +#include <api/object.h> +#include <api/qtobject.h> +#include <main/manager.h> + +#include <kdebug.h> + +// The as version() published versionnumber of this kross-module. +#define KROSS_KEXIAPP_VERSION 1 + +extern "C" +{ + /** + * Exported an loadable function as entry point to use + * the \a KexiAppModule. + */ + Kross::Api::Object* KDE_EXPORT init_module(Kross::Api::Manager* manager) + { + return new Kross::KexiApp::KexiAppModule(manager); + } +} + +namespace Kross { namespace KexiApp { + + /// \internal + class KexiAppModulePrivate + { + public: + Kross::Api::Manager* manager; + }; + +}} + +using namespace Kross::KexiApp; + +KexiAppModule::KexiAppModule(Kross::Api::Manager* manager) + : Kross::Api::Module("KexiApp") + , d(new KexiAppModulePrivate()) +{ + kdDebug() << "Kross::KexiApp::KexiAppModule Ctor" << endl; + + d->manager = manager; + + Kross::Api::Object::Ptr mainwinobject = manager->getChild("KexiMainWindow"); + if(mainwinobject) { + Kross::Api::QtObject* mainwinqtobject = dynamic_cast< Kross::Api::QtObject* >( mainwinobject.data() ); + if(mainwinqtobject) { + ::KexiMainWindow* mainwin = dynamic_cast< ::KexiMainWindow* >( mainwinqtobject->getObject() ); + if(mainwin) { + addChild( "version", new Kross::Api::Variant(KROSS_KEXIAPP_VERSION) ); + addChild( new KexiAppMainWindow(mainwin) ); + return; + } + else kdDebug()<<"Kross::KexiApp::KexiAppModule: Failed to determinate KexiMainWindow instance"<<endl; + } + else kdDebug()<<"Kross::KexiApp::KexiAppModule: Failed to cast 'KexiMainWindow' to a QtObject"<<endl; + } + else kdDebug()<<"Kross::KexiApp::KexiAppModule: No such object 'KexiMainWindow'"<<endl; + + throw Kross::Api::Exception::Ptr( new Kross::Api::Exception("There was no 'KexiMainWindow' published.") ); +} + +KexiAppModule::~KexiAppModule() +{ + kdDebug() << "Kross::KexiApp::KexiAppModule Dtor" << endl; + delete d; +} + + +const QString KexiAppModule::getClassName() const +{ + return "Kross::KexiApp::KexiAppModule"; +} + diff --git a/kexi/plugins/scripting/kexiapp/kexiappmodule.h b/kexi/plugins/scripting/kexiapp/kexiappmodule.h new file mode 100644 index 00000000..08ed71f0 --- /dev/null +++ b/kexi/plugins/scripting/kexiapp/kexiappmodule.h @@ -0,0 +1,76 @@ +/*************************************************************************** + * kexiappmodule.h + * This file is part of the KDE project + * copyright (C)2004-2005 by Sebastian Sauer (mail@dipe.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 KROSS_KEXIAPP_KEXIAPPMODULE_H +#define KROSS_KEXIAPP_KEXIAPPMODULE_H + +#include <qstring.h> +#include <qvariant.h> + +#include <api/module.h> + +namespace Kross { namespace Api { + class Manager; +}} + +namespace Kross { + +/** + * Wrapper around the Kexi-application to access runtime + * information a running Kexi-application likes to + * provide. + */ +namespace KexiApp { + + class KexiAppModulePrivate; + + /** + * The Kexi-application module which provides us the + * main entrypoint to communicate with a running + * Kexi-application. + */ + class KexiAppModule : public Kross::Api::Module + { + public: + + /** + * Constructor. + * + * \param manager The \a Kross::Api::Manager singleton + * instance used to access this module. + */ + KexiAppModule(Kross::Api::Manager* manager); + + /** + * Destructor. + */ + virtual ~KexiAppModule(); + + /// \see Kross::Api::Object::getClassName + virtual const QString getClassName() const; + + private: + /// Private d-pointer class. + KexiAppModulePrivate* d; + }; + +}} + +#endif + diff --git a/kexi/plugins/scripting/kexiapp/kexiapppart.cpp b/kexi/plugins/scripting/kexiapp/kexiapppart.cpp new file mode 100644 index 00000000..23d6c2f5 --- /dev/null +++ b/kexi/plugins/scripting/kexiapp/kexiapppart.cpp @@ -0,0 +1,46 @@ +/*************************************************************************** + * kexiapppart.cpp + * This file is part of the KDE project + * copyright (C)2006 by Sebastian Sauer (mail@dipe.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 "kexiapppart.h" + +#include "core/kexipart.h" +#include "core/kexipartitem.h" +//#include "core/kexiproject.h" + +using namespace Kross::KexiApp; + +KexiAppPartItem::KexiAppPartItem(KexiPart::Item* item) + : Kross::Api::Class<KexiAppPartItem>("KexiAppPartItem") +{ + this->addFunction0<Kross::Api::Variant>("identifier", item, &::KexiPart::Item::identifier ); + + this->addFunction1<void, Kross::Api::Variant>("setIdentifier", item, &::KexiPart::Item::setIdentifier ); + + this->addFunction0<Kross::Api::Variant>("mimeType", item, &::KexiPart::Item::mimeType ); + this->addFunction1<void, Kross::Api::Variant>("setMimeType", item, &::KexiPart::Item::setMimeType ); + + this->addFunction0<Kross::Api::Variant>("name", item, &::KexiPart::Item::name ); + this->addFunction1<void, Kross::Api::Variant>("setName", item, &::KexiPart::Item::setName ); + + this->addFunction0<Kross::Api::Variant>("caption", item, &::KexiPart::Item::caption ); + this->addFunction1<void, Kross::Api::Variant>("setCaption", item, &::KexiPart::Item::setCaption ); + + this->addFunction0<Kross::Api::Variant>("description", item, &::KexiPart::Item::description ); + this->addFunction1<void, Kross::Api::Variant>("setDescription", item, &::KexiPart::Item::setDescription ); +} diff --git a/kexi/plugins/scripting/kexiapp/kexiapppart.h b/kexi/plugins/scripting/kexiapp/kexiapppart.h new file mode 100644 index 00000000..5f55d6bf --- /dev/null +++ b/kexi/plugins/scripting/kexiapp/kexiapppart.h @@ -0,0 +1,56 @@ +/*************************************************************************** + * kexiapppart.h + * This file is part of the KDE project + * copyright (C)2006 by Sebastian Sauer (mail@dipe.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 KROSS_KEXIAPP_KEXIAPPPART_H +#define KROSS_KEXIAPP_KEXIAPPPART_H + +#include <qstring.h> +#include <qvariant.h> + +#include <api/object.h> +#include <api/variant.h> +#include <api/class.h> + +// Forward declarations. +namespace KexiPart { + class Item; + class Part; +} + +namespace Kross { namespace KexiApp { + + /** + * Class to handle Kexi Part::Item instance. + */ + class KexiAppPartItem : public Kross::Api::Class<KexiAppPartItem> + { + public: + KexiAppPartItem(KexiPart::Item*); + virtual ~KexiAppPartItem() {} + virtual const QString getClassName() const { return "Kross::KexiApp::KexiAppPartItem"; } + + KexiPart::Item* item() { return m_item; } + private: + KexiPart::Item* m_item; + }; + +}} + +#endif + diff --git a/kexi/plugins/scripting/kexidb.doxyfile b/kexi/plugins/scripting/kexidb.doxyfile new file mode 100644 index 00000000..e40a378e --- /dev/null +++ b/kexi/plugins/scripting/kexidb.doxyfile @@ -0,0 +1,324 @@ +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +PROJECT_NAME = KrossKexiDB +PROJECT_NUMBER = 1.0. +OUTPUT_DIRECTORY = kexidbdocs +CREATE_SUBDIRS = NO +OUTPUT_LANGUAGE = English +USE_WINDOWS_ENCODING = NO +BRIEF_MEMBER_DESC = YES +REPEAT_BRIEF = YES +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all inherited +# members of a class in the documentation of that class as if those members were +# ordinary class members. Constructors, destructors and assignment operators of +# the base classes will not be shown. +INLINE_INHERITED_MEMB = NO +##INLINE_INHERITED_MEMB = YES + +FULL_PATH_NAMES = NO +#STRIP_FROM_PATH = /home/snoopy/ +STRIP_FROM_INC_PATH = +SHORT_NAMES = YES +JAVADOC_AUTOBRIEF = NO +MULTILINE_CPP_IS_BRIEF = NO + +# If the DETAILS_AT_TOP tag is set to YES then Doxygen +# will output the detailed description near the top, like JavaDoc. +# If set to NO, the detailed description appears after the member +# documentation. +DETAILS_AT_TOP = YES + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# reimplements. +INHERIT_DOCS = YES + +DISTRIBUTE_GROUP_DOC = NO +TAB_SIZE = 8 +ALIASES = +OPTIMIZE_OUTPUT_FOR_C = NO +OPTIMIZE_OUTPUT_JAVA = NO +SUBGROUPING = NO +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- +EXTRACT_ALL = NO +EXTRACT_PRIVATE = YES +EXTRACT_STATIC = NO +EXTRACT_LOCAL_CLASSES = YES +EXTRACT_LOCAL_METHODS = YES +HIDE_UNDOC_MEMBERS = YES +HIDE_UNDOC_CLASSES = YES +HIDE_FRIEND_COMPOUNDS = YES +HIDE_IN_BODY_DOCS = YES +INTERNAL_DOCS = NO + +CASE_SENSE_NAMES = YES +HIDE_SCOPE_NAMES = YES +SHOW_INCLUDE_FILES = NO +INLINE_INFO = NO +SORT_MEMBER_DOCS = NO +SORT_BRIEF_DOCS = NO +SORT_BY_SCOPE_NAME = NO +GENERATE_TODOLIST = NO +GENERATE_TESTLIST = NO +GENERATE_BUGLIST = NO +GENERATE_DEPRECATEDLIST= NO +ENABLED_SECTIONS = +MAX_INITIALIZER_LINES = 30 +SHOW_USED_FILES = NO +SHOW_DIRECTORIES = NO +FILE_VERSION_FILTER = +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +QUIET = NO +WARNINGS = YES +WARN_IF_UNDOCUMENTED = NO +WARN_IF_DOC_ERROR = YES +WARN_NO_PARAMDOC = NO +WARN_FORMAT = "$file:$line: $text" +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +INPUT = ../kexidb/ +IMAGE_PATH = +FILE_PATTERNS = *.cpp *.h *.dox +RECURSIVE = YES + +EXCLUDE = +EXCLUDE_SYMLINKS = YES +EXCLUDE_PATTERNS = config.h *.moc.cpp + +EXAMPLE_PATH = +EXAMPLE_PATTERNS = * +EXAMPLE_RECURSIVE = NO + +INPUT_FILTER = +FILTER_PATTERNS = +FILTER_SOURCE_FILES = NO + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. +STRIP_CODE_COMMENTS = YES + +REFERENCED_BY_RELATION = NO +REFERENCES_RELATION = NO +VERBATIM_HEADERS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +ALPHABETICAL_INDEX = NO +COLS_IN_ALPHA_INDEX = 4 +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +GENERATE_HTML = YES +HTML_OUTPUT = html +HTML_FILE_EXTENSION = .html +HTML_HEADER = +HTML_FOOTER = +HTML_STYLESHEET = +HTML_ALIGN_MEMBERS = YES +GENERATE_HTMLHELP = NO +CHM_FILE = +HHC_LOCATION = +GENERATE_CHI = NO +BINARY_TOC = NO +TOC_EXPAND = NO +DISABLE_INDEX = NO +ENUM_VALUES_PER_LINE = 4 +GENERATE_TREEVIEW = NO +TREEVIEW_WIDTH = 250 + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +GENERATE_LATEX = YES +LATEX_OUTPUT = latex +LATEX_CMD_NAME = latex +MAKEINDEX_CMD_NAME = makeindex +PAPER_TYPE = a4wide +EXTRA_PACKAGES = +LATEX_HEADER = + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. +COMPACT_LATEX = YES + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. +##PDF_HYPERLINKS = NO +PDF_HYPERLINKS = YES + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. +##LATEX_BATCHMODE = NO +LATEX_BATCHMODE = YES + +LATEX_HIDE_INDICES = NO + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +GENERATE_RTF = NO +RTF_OUTPUT = rtf +COMPACT_RTF = NO +RTF_HYPERLINKS = NO +RTF_STYLESHEET_FILE = +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +GENERATE_MAN = NO +MAN_OUTPUT = man +MAN_EXTENSION = .3 +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +GENERATE_XML = NO +XML_OUTPUT = xml +XML_SCHEMA = +XML_DTD = +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +GENERATE_PERLMOD = NO +PERLMOD_LATEX = NO +PERLMOD_PRETTY = YES +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +ENABLE_PREPROCESSING = YES +MACRO_EXPANSION = NO +EXPAND_ONLY_PREDEF = NO +SEARCH_INCLUDES = NO +INCLUDE_PATH = +INCLUDE_FILE_PATTERNS = +PREDEFINED = +EXPAND_AS_DEFINED = +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +TAGFILES = +GENERATE_TAGFILE = krosskexidb.tag +ALLEXTERNALS = NO +EXTERNAL_GROUPS = NO +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) +HAVE_DOT = NO + +CLASS_DIAGRAMS = NO +HIDE_UNDOC_RELATIONS = NO +CLASS_GRAPH = NO +COLLABORATION_GRAPH = NO +GROUP_GRAPHS = NO + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similiar to the OMG's Unified Modeling +# Language. +UML_LOOK = NO + +TEMPLATE_RELATIONS = NO +INCLUDE_GRAPH = NO +INCLUDED_BY_GRAPH = NO +##CALL_GRAPH = YES +GRAPHICAL_HIERARCHY = NO +DIRECTORY_GRAPH = NO +DOT_IMAGE_FORMAT = png +DOT_PATH = +DOTFILE_DIRS = +MAX_DOT_GRAPH_WIDTH = 1024 +MAX_DOT_GRAPH_HEIGHT = 1024 +MAX_DOT_GRAPH_DEPTH = 1000 +DOT_TRANSPARENT = NO +DOT_MULTI_TARGETS = NO +GENERATE_LEGEND = NO +DOT_CLEANUP = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to the search engine +#--------------------------------------------------------------------------- + +SEARCHENGINE = NO diff --git a/kexi/plugins/scripting/kexidb/Makefile.am b/kexi/plugins/scripting/kexidb/Makefile.am new file mode 100644 index 00000000..12f82501 --- /dev/null +++ b/kexi/plugins/scripting/kexidb/Makefile.am @@ -0,0 +1,30 @@ +include $(top_srcdir)/kexi/Makefile.global + +INCLUDES = -I$(top_srcdir)/kexi $(KROSS_INCLUDES) $(all_includes) + +kde_module_LTLIBRARIES = krosskexidb.la + +krosskexidb_la_SOURCES = \ + kexidbfield.cpp \ + kexidbfieldlist.cpp \ + kexidbschema.cpp \ + kexidbparser.cpp \ + kexidbcursor.cpp \ + kexidbtransaction.cpp \ + kexidbconnectiondata.cpp \ + kexidbconnection.cpp \ + kexidbdriver.cpp \ + kexidbdrivermanager.cpp \ + kexidbmodule.cpp + +krosskexidb_la_LDFLAGS = $(all_libraries) $(KDE_PLUGIN) $(VER_INFO) -module +krosskexidb_la_LIBADD = \ + $(LIB_QT) \ + $(LIB_KDECORE) \ + $(LIB_KROSS_API) \ + $(LIB_KROSS_MAIN) \ + $(top_builddir)/kexi/kexidb/libkexidb.la \ + $(top_builddir)/kexi/kexidb/parser/libkexidbparser.la + +METASOURCES = AUTO +SUBDIRS = . diff --git a/kexi/plugins/scripting/kexidb/kexidbconnection.cpp b/kexi/plugins/scripting/kexidb/kexidbconnection.cpp new file mode 100644 index 00000000..d3b7cc76 --- /dev/null +++ b/kexi/plugins/scripting/kexidb/kexidbconnection.cpp @@ -0,0 +1,221 @@ +/*************************************************************************** + * kexidbconnection.cpp + * This file is part of the KDE project + * copyright (C)2004-2005 by Sebastian Sauer (mail@dipe.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 "kexidbconnection.h" +#include "kexidbconnectiondata.h" +#include "kexidbdrivermanager.h" +#include "kexidbdriver.h" +#include "kexidbcursor.h" +#include "kexidbfieldlist.h" +#include "kexidbschema.h" +#include "kexidbtransaction.h" +#include "kexidbparser.h" + +#include <api/exception.h> + +#include <kdebug.h> + +#include <kexidb/transaction.h> + +using namespace Kross::KexiDB; + +KexiDBConnection::KexiDBConnection(::KexiDB::Connection* connection, KexiDBDriver* driver, KexiDBConnectionData* connectiondata) + : Kross::Api::Class<KexiDBConnection>("KexiDBConnection") + , m_connection(connection) + , m_connectiondata(connectiondata ? connectiondata : new KexiDBConnectionData(connection->data())) + , m_driver(driver ? driver : new KexiDBDriver(connection->driver())) +{ + this->addFunction0< Kross::Api::Variant >("hadError", this, &KexiDBConnection::hadError); + this->addFunction0< Kross::Api::Variant >("lastError", this, &KexiDBConnection::lastError); + + this->addFunction0< KexiDBConnectionData >("data", this, &KexiDBConnection::data); + this->addFunction0< KexiDBDriver >("driver", this, &KexiDBConnection::driver); + + this->addFunction0< Kross::Api::Variant >("connect", this, &KexiDBConnection::connect); + this->addFunction0< Kross::Api::Variant >("isConnected", this, &KexiDBConnection::isConnected); + this->addFunction0< Kross::Api::Variant >("disconnect", this, &KexiDBConnection::disconnect); + + this->addFunction1< Kross::Api::Variant, Kross::Api::Variant >("databaseExists", this, &KexiDBConnection::databaseExists); + this->addFunction0< Kross::Api::Variant >("currentDatabase", this, &KexiDBConnection::currentDatabase); + this->addFunction0< Kross::Api::Variant >("databaseNames", this, &KexiDBConnection::databaseNames); + this->addFunction0< Kross::Api::Variant >("isDatabaseUsed", this, &KexiDBConnection::isDatabaseUsed); + this->addFunction1< Kross::Api::Variant, Kross::Api::Variant >("useDatabase", this, &KexiDBConnection::useDatabase); + this->addFunction0< Kross::Api::Variant >("closeDatabase", this, &KexiDBConnection::closeDatabase); + + this->addFunction0< Kross::Api::Variant >("tableNames", this, &KexiDBConnection::tableNames); + this->addFunction0< Kross::Api::Variant >("queryNames", this, &KexiDBConnection::queryNames); + + this->addFunction1< KexiDBCursor, Kross::Api::Variant >("executeQueryString", this, &KexiDBConnection::executeQueryString); + this->addFunction1< KexiDBCursor, KexiDBQuerySchema >("executeQuerySchema", this, &KexiDBConnection::executeQuerySchema); + + addFunction("insertRecord", &KexiDBConnection::insertRecord); + + this->addFunction1< Kross::Api::Variant, Kross::Api::Variant >("createDatabase", this, &KexiDBConnection::createDatabase); + this->addFunction1< Kross::Api::Variant, Kross::Api::Variant >("dropDatabase", this, &KexiDBConnection::dropDatabase); + + this->addFunction1< Kross::Api::Variant, KexiDBTableSchema >("createTable", this, &KexiDBConnection::createTable); + this->addFunction1< Kross::Api::Variant, Kross::Api::Variant >("dropTable", this, &KexiDBConnection::dropTable); + this->addFunction2< Kross::Api::Variant, KexiDBTableSchema, KexiDBTableSchema >("alterTable", this, &KexiDBConnection::alterTable); + this->addFunction2< Kross::Api::Variant, KexiDBTableSchema, Kross::Api::Variant >("alterTableName", this, &KexiDBConnection::alterTableName); + + this->addFunction1< KexiDBTableSchema, Kross::Api::Variant >("tableSchema", this, &KexiDBConnection::tableSchema); + this->addFunction1< Kross::Api::Variant, KexiDBTableSchema >("isEmptyTable", this, &KexiDBConnection::isEmptyTable); + this->addFunction1< KexiDBQuerySchema, Kross::Api::Variant >("querySchema", this, &KexiDBConnection::querySchema); + + this->addFunction0< Kross::Api::Variant >("autoCommit", this, &KexiDBConnection::autoCommit); + this->addFunction1< Kross::Api::Variant, Kross::Api::Variant >("setAutoCommit", this, &KexiDBConnection::setAutoCommit); + + this->addFunction0< KexiDBTransaction >("beginTransaction", this, &KexiDBConnection::beginTransaction); + this->addFunction1< Kross::Api::Variant, KexiDBTransaction >("commitTransaction", this, &KexiDBConnection::commitTransaction); + this->addFunction1< Kross::Api::Variant, KexiDBTransaction >("rollbackTransaction", this, &KexiDBConnection::rollbackTransaction); + this->addFunction0< KexiDBTransaction >("defaultTransaction", this, &KexiDBConnection::defaultTransaction); + this->addFunction1< void, KexiDBTransaction >("setDefaultTransaction", this, &KexiDBConnection::setDefaultTransaction); + this->addFunction0<Kross::Api::List>("transactions", this, &KexiDBConnection::transactions); + + this->addFunction0< KexiDBParser >("parser", this, &KexiDBConnection::parser); +} + +KexiDBConnection::~KexiDBConnection() { +} + +const QString KexiDBConnection::getClassName() const { + return "Kross::KexiDB::KexiDBConnection"; +} + +::KexiDB::Connection* KexiDBConnection::connection() const { + if(! m_connection) + throw Kross::Api::Exception::Ptr( new Kross::Api::Exception(QString("KexiDB::Connection is NULL.")) ); + //if(m_connection->error()) + // throw Kross::Api::Exception::Ptr( new Kross::Api::Exception(QString("KexiDB::Connection error: %1").arg(m_connection->errorMsg())) ); + return m_connection; +} + +bool KexiDBConnection::hadError() const { return connection()->error(); } +const QString KexiDBConnection::lastError() const { return connection()->errorMsg(); } + +KexiDBConnectionData* KexiDBConnection::data() { return m_connectiondata.data(); } +KexiDBDriver* KexiDBConnection::driver() { return m_driver.data(); } + +bool KexiDBConnection::connect() { return connection()->connect(); } +bool KexiDBConnection::isConnected() { return connection()->isConnected(); } +bool KexiDBConnection::disconnect() { return connection()->disconnect(); } + +bool KexiDBConnection::isReadOnly() const { return connection()->isReadOnly(); } + +bool KexiDBConnection::databaseExists(const QString& dbname) { return connection()->databaseExists(dbname); } +const QString KexiDBConnection::currentDatabase() const { return connection()->currentDatabase(); } +const QStringList KexiDBConnection::databaseNames() const { return connection()->databaseNames(); } +bool KexiDBConnection::isDatabaseUsed() const { return connection()->isDatabaseUsed(); } +bool KexiDBConnection::useDatabase(const QString& dbname) { return connection()->databaseExists(dbname) && m_connection->useDatabase(dbname); } +bool KexiDBConnection::closeDatabase() { return connection()->closeDatabase(); } + +const QStringList KexiDBConnection::allTableNames() const { return connection()->tableNames(true); } +const QStringList KexiDBConnection::tableNames() const { return connection()->tableNames(false); } + +const QStringList KexiDBConnection::queryNames() const { + bool ok = true; + QStringList queries = connection()->objectNames(::KexiDB::QueryObjectType, &ok); + if(! ok) throw Kross::Api::Exception::Ptr( new Kross::Api::Exception(QString("Failed to determinate querynames.")) ); + return queries; +} + +KexiDBCursor* KexiDBConnection::executeQueryString(const QString& sqlquery) { + // The ::KexiDB::Connection::executeQuery() method does not check if we pass a valid SELECT-statement + // or e.g. a DROP TABLE operation. So, let's check for such dangerous operations right now. + ::KexiDB::Parser parser( connection() ); + if(! parser.parse(sqlquery)) + throw Kross::Api::Exception::Ptr( new Kross::Api::Exception(QString("Failed to parse query: %1 %2").arg(parser.error().type()).arg(parser.error().error())) ); + if( parser.query() == 0 || parser.operation() != ::KexiDB::Parser::OP_Select ) + throw Kross::Api::Exception::Ptr( new Kross::Api::Exception(QString("Invalid query operation \"%1\"").arg(parser.operationString()) ) ); + ::KexiDB::Cursor* cursor = connection()->executeQuery(sqlquery); + return cursor ? new KexiDBCursor(cursor) : 0; +} + +KexiDBCursor* KexiDBConnection::executeQuerySchema(KexiDBQuerySchema* queryschema) { + ::KexiDB::Cursor* cursor = connection()->executeQuery( *queryschema->queryschema() ); + return cursor ? new KexiDBCursor(cursor) : 0; +} + +/*TODO +bool KexiDBConnection::insertRecordIntoFieldlist(KexiDBFieldList* fieldlist, QValueList<QVariant> values) { + return connection()->insertRecord(*fieldlist->fieldlist(), values); +} + +bool KexiDBConnection::insertRecordIntoTable(KexiDBTableSchema* tableschema, QValueList<QVariant> values) { + return connection()->insertRecord(*tableschema->tableschema(), values); +} +*/ +Kross::Api::Object::Ptr KexiDBConnection::insertRecord(Kross::Api::List::Ptr args) { + QValueList<QVariant> values = Kross::Api::Variant::toList(args->item(1)); + Kross::Api::Object::Ptr obj = args->item(0); + if(obj->getClassName() == "Kross::KexiDB::KexiDBFieldList") + return new Kross::Api::Variant( + QVariant(connection()->insertRecord( + *Kross::Api::Object::fromObject<KexiDBFieldList>(obj)->fieldlist(), + values + ), 0)); + return new Kross::Api::Variant( + QVariant(connection()->insertRecord( + *Kross::Api::Object::fromObject<KexiDBTableSchema>(obj)->tableschema(), + values + ), 0)); +} + +bool KexiDBConnection::createDatabase(const QString& dbname) { return connection()->createDatabase(dbname); } +bool KexiDBConnection::dropDatabase(const QString& dbname) { return connection()->dropDatabase(dbname); } + +bool KexiDBConnection::createTable(KexiDBTableSchema* tableschema) { return connection()->createTable(tableschema->tableschema(), false); } +bool KexiDBConnection::dropTable(const QString& tablename) { return true == connection()->dropTable(tablename); } +bool KexiDBConnection::alterTable(KexiDBTableSchema* fromschema, KexiDBTableSchema* toschema) { return true == connection()->alterTable(*fromschema->tableschema(), *toschema->tableschema()); } +bool KexiDBConnection::alterTableName(KexiDBTableSchema* tableschema, const QString& newtablename) { return connection()->alterTableName(*tableschema->tableschema(), newtablename); } + +KexiDBTableSchema* KexiDBConnection::tableSchema(const QString& tablename) const { + ::KexiDB::TableSchema* tableschema = connection()->tableSchema(tablename); + return tableschema ? new KexiDBTableSchema(tableschema) : 0; +} + +bool KexiDBConnection::isEmptyTable(KexiDBTableSchema* tableschema) const { + bool success; + bool notempty = connection()->isEmpty(*tableschema->tableschema(), success); + return (! (success && notempty)); +} + +KexiDBQuerySchema* KexiDBConnection::querySchema(const QString& queryname) const { + ::KexiDB::QuerySchema* queryschema = connection()->querySchema(queryname); + return queryschema ? new KexiDBQuerySchema(queryschema) : 0; +} + +bool KexiDBConnection::autoCommit() const { return connection()->autoCommit(); } +bool KexiDBConnection::setAutoCommit(bool enabled) { return connection()->setAutoCommit(enabled); } + +KexiDBTransaction* KexiDBConnection::beginTransaction() { + ::KexiDB::Transaction t = connection()->beginTransaction(); + return new KexiDBTransaction(t); +} + +bool KexiDBConnection::commitTransaction(KexiDBTransaction* transaction) { return connection()->commitTransaction( transaction->transaction() ); } +bool KexiDBConnection::rollbackTransaction(KexiDBTransaction* transaction) { return connection()->rollbackTransaction( transaction->transaction() ); } +KexiDBTransaction* KexiDBConnection::defaultTransaction() { return new KexiDBTransaction( connection()->defaultTransaction() ); } +void KexiDBConnection::setDefaultTransaction(KexiDBTransaction* transaction) { connection()->setDefaultTransaction( transaction->transaction() ); } + +Kross::Api::List* KexiDBConnection::transactions() { + return new Kross::Api::ListT<KexiDBTransaction>( connection()->transactions() ); +} + +KexiDBParser* KexiDBConnection::parser() { return new KexiDBParser(this, new ::KexiDB::Parser(connection())); } diff --git a/kexi/plugins/scripting/kexidb/kexidbconnection.h b/kexi/plugins/scripting/kexidb/kexidbconnection.h new file mode 100644 index 00000000..7e1a7d3a --- /dev/null +++ b/kexi/plugins/scripting/kexidb/kexidbconnection.h @@ -0,0 +1,194 @@ +/*************************************************************************** + * kexidbconnection.h + * This file is part of the KDE project + * copyright (C)2004-2005 by Sebastian Sauer (mail@dipe.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 KROSS_KEXIDB_KEXIDBCONNECTION_H +#define KROSS_KEXIDB_KEXIDBCONNECTION_H + +#include <qstring.h> +#include <ksharedptr.h> + +#include <api/object.h> +#include <api/variant.h> +#include <api/list.h> +#include <api/class.h> + +//#include <kexidb/driver.h> +#include <kexidb/connection.h> + +namespace Kross { namespace KexiDB { + + // Forward declarations. + class KexiDBDriver; + class KexiDBConnectionData; + class KexiDBCursor; + class KexiDBTableSchema; + class KexiDBQuerySchema; + class KexiDBTransaction; + class KexiDBParser; + + /** + * A connection to a database. + * + * Example (in Python) ; + * @code + * # Import the kexidb module. + * import krosskexidb + * # Get the drivermanager. + * drivermanager = krosskexidb.DriverManager() + * # We need a connectiondata object. + * connectiondata = drivermanager.createConnectionData() + * # Fill the new connectiondata object with what we need to connect. + * connectiondata.setFileName("/home/user/kexisqlite3file.kexi") + * # Create the database-driver to access the SQLite3 backend. + * driver = drivermanager.driver("SQLite3") + * # Create the connection now. + * connection = driver.createConnection(connectiondata) + * # Establish the connection. + * if not connection.connect(): raise("Failed to connect with db") + * # Open database for usage. The filebased driver uses the filename as databasename. + * if not connection.useDatabase("/home/user/kexisqlite3file.kexi"): raise("Failed to use db") + * @endcode + */ + class KexiDBConnection : public Kross::Api::Class<KexiDBConnection> + { + public: + KexiDBConnection(::KexiDB::Connection* connection, KexiDBDriver* driver = 0, KexiDBConnectionData* connectiondata = 0); + virtual ~KexiDBConnection(); + virtual const QString getClassName() const; + + private: + + /** Return true if there was an error during last operation on the database. */ + bool hadError() const; + /** Return the last errormessage. */ + const QString lastError() const; + + /** Return the KexiDBConnectionData object used to create this connection. */ + KexiDBConnectionData* data(); + /** Return the KexiDBDriver object this connection belongs too. */ + KexiDBDriver* driver(); + + /** Try to connect and return true if we are successfully connected now. */ + bool connect(); + /** Return true if we are connected. */ + bool isConnected(); + /** Disconnect and return true if we are successfully disconnected now. */ + bool disconnect(); + + /** Returns true if the connection is read-only. */ + bool isReadOnly() const; + + /** Return true if the as argument passed databasename exists. */ + bool databaseExists(const QString& dbname); + /** Return the name of currently used database for this connection or empty + string if there is no used database. */ + const QString currentDatabase() const; + /** Return list of database names for opened connection. */ + const QStringList databaseNames() const; + /** Return true if connection is properly established. */ + bool isDatabaseUsed() const; + /** Opens an existing database specified by the as argument passed databasename + and returns true if the database is used now. */ + bool useDatabase(const QString& dbname); + /** Closes currently used database for this connection. */ + bool closeDatabase(); + + /** Return names of all table schemas stored in currently used database include the + internal KexiDB system table names (kexi__*) */ + const QStringList allTableNames() const; + /** Return names of all table schemas without the internal KexiDB system table names (kexi__*) */ + const QStringList tableNames() const; + /** Return names of all query schemas stored in currently used database. */ + const QStringList queryNames() const; + + /** Executes query described by the as argument passed sqlstatement-string. Returns the + opened cursor created for results of this query. */ + KexiDBCursor* executeQueryString(const QString& sqlquery); + /** Executes query described by the as argument passed KexiDBQuerySchema object. Returns + the opened cursor created for results of this query. */ + KexiDBCursor* executeQuerySchema(KexiDBQuerySchema* queryschema); + +//TODO replace following method with a proxymethod. + /** Inserts the as argument passed KexiDBField object. */ + Kross::Api::Object::Ptr insertRecord(Kross::Api::List::Ptr); + + /** Creates new database with the as argument passed databasename. */ + bool createDatabase(const QString& dbname); + /** Drops the as argument passed databasename. */ + bool dropDatabase(const QString& dbname); + + /** Creates table defined by the as argument passed KexiTableSchema object. */ + bool createTable(KexiDBTableSchema* tableschema); + /** Drops table defined by the as argument passed KexiDBTableSchema object. */ + bool dropTable(const QString& tablename); + /** Alters the as first argument passed KexiDBTableSchema object using the as + second argument passed KexiDBTableSchema. */ + bool alterTable(KexiDBTableSchema* fromschema, KexiDBTableSchema* toschema); + /** Alters the tablename of the as first argument passed KexiDBTableSchema into + the as second argument passed new tablename. */ + bool alterTableName(KexiDBTableSchema* tableschema, const QString& newtablename); + + /** Returns the KexiDBTableSchema object of the table matching to the as argument + passed tablename. */ + KexiDBTableSchema* tableSchema(const QString& tablename) const; + /** Returns true if there is at least one valid record in the as argument passed tablename. */ + bool isEmptyTable(KexiDBTableSchema* tableschema) const; + /** Returns the KexiDBQuerySchema object of the query matching to the as argument passed queryname. */ + KexiDBQuerySchema* querySchema(const QString& queryname) const; + + /** Return true if the \"auto commit\" option is on. */ + bool autoCommit() const; + /** Set the auto commit option. This does not affect currently started transactions and can + be changed even when connection is not established. */ + bool setAutoCommit(bool enabled); + + /** Creates new transaction handle and starts a new transaction. */ + KexiDBTransaction* beginTransaction(); + /** Commits the as rgument passed KexiDBTransaction object. */ + bool commitTransaction(KexiDBTransaction* transaction); + /** Rollback the as rgument passed KexiDBTransaction object. */ + bool rollbackTransaction(KexiDBTransaction* transaction); + /** Return the KEXIDBTransaction object for default transaction for this connection. */ + KexiDBTransaction* defaultTransaction(); + /** Sets default transaction that will be used as context for operations on data in opened + database for this connection. */ + void setDefaultTransaction(KexiDBTransaction* transaction); + + /** Return list of currently active KexiDBTransaction objects. */ + Kross::Api::List* transactions(); + + /** Return a KexiDBParser object. */ + KexiDBParser* parser(); + + private: + ::KexiDB::Connection* connection() const; + ::KexiDB::Connection* m_connection; + + KSharedPtr<KexiDBConnectionData> m_connectiondata; + KSharedPtr<KexiDBDriver> m_driver; + + /// Initialize the class instance. + void initialize(); + + }; + +}} + +#endif + diff --git a/kexi/plugins/scripting/kexidb/kexidbconnectiondata.cpp b/kexi/plugins/scripting/kexidb/kexidbconnectiondata.cpp new file mode 100644 index 00000000..61b81d3e --- /dev/null +++ b/kexi/plugins/scripting/kexidb/kexidbconnectiondata.cpp @@ -0,0 +1,112 @@ +/*************************************************************************** + * kexidbconnectiondata.cpp + * This file is part of the KDE project + * copyright (C)2004-2005 by Sebastian Sauer (mail@dipe.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 "kexidbconnectiondata.h" + +#include <qvariant.h> + +using namespace Kross::KexiDB; + +KexiDBConnectionData::KexiDBConnectionData(::KexiDB::ConnectionData* data) + : Kross::Api::Class<KexiDBConnectionData>("KexiDBConnectionData") + , m_data(data) +{ + this->addFunction0< Kross::Api::Variant >("caption", this, &KexiDBConnectionData::caption); + this->addFunction1< void, Kross::Api::Variant >("setCaption", this, &KexiDBConnectionData::setCaption); + + this->addFunction0< Kross::Api::Variant >("description", this, &KexiDBConnectionData::description); + this->addFunction1< void, Kross::Api::Variant >("setDescription", this, &KexiDBConnectionData::setDescription); + + this->addFunction0< Kross::Api::Variant >("driverName", this, &KexiDBConnectionData::driverName); + this->addFunction1< void, Kross::Api::Variant >("setDriverName", this, &KexiDBConnectionData::setDriverName); + + this->addFunction0< Kross::Api::Variant >("localSocketFileUsed", this, &KexiDBConnectionData::localSocketFileUsed); + this->addFunction1< void, Kross::Api::Variant >("setLocalSocketFileUsed", this, &KexiDBConnectionData::setLocalSocketFileUsed); + + this->addFunction0< Kross::Api::Variant >("localSocketFileName", this, &KexiDBConnectionData::localSocketFileName); + this->addFunction1< void, Kross::Api::Variant >("setLocalSocketFileName", this, &KexiDBConnectionData::setLocalSocketFileName); + + this->addFunction0< Kross::Api::Variant >("databaseName", this, &KexiDBConnectionData::databaseName); + this->addFunction1< void, Kross::Api::Variant >("setDatabaseName", this, &KexiDBConnectionData::setDatabaseName); + + this->addFunction0< Kross::Api::Variant >("hostName", this, &KexiDBConnectionData::hostName); + this->addFunction1< void, Kross::Api::Variant >("setHostName", this, &KexiDBConnectionData::setHostName); + + this->addFunction0< Kross::Api::Variant >("port", this, &KexiDBConnectionData::port); + this->addFunction1< void, Kross::Api::Variant >("setPort", this, &KexiDBConnectionData::setPort); + + this->addFunction0< Kross::Api::Variant >("password", this, &KexiDBConnectionData::password); + this->addFunction1< void, Kross::Api::Variant >("setPassword", this, &KexiDBConnectionData::setPassword); + + this->addFunction0< Kross::Api::Variant >("userName", this, &KexiDBConnectionData::userName); + this->addFunction1< void, Kross::Api::Variant >("setUserName", this, &KexiDBConnectionData::setUserName); + + this->addFunction0< Kross::Api::Variant >("fileName", this, &KexiDBConnectionData::fileName); + this->addFunction1< void, Kross::Api::Variant >("setFileName", this, &KexiDBConnectionData::setFileName); + + this->addFunction0< Kross::Api::Variant >("dbPath", this, &KexiDBConnectionData::dbPath); + this->addFunction0< Kross::Api::Variant >("dbFileName", this, &KexiDBConnectionData::dbFileName); + this->addFunction0< Kross::Api::Variant >("serverInfoString", this, &KexiDBConnectionData::serverInfoString); +} + +KexiDBConnectionData::~KexiDBConnectionData() +{ + //delete m_data; +} + +const QString KexiDBConnectionData::getClassName() const +{ + return "Kross::KexiDB::KexiDBConnectionData"; +} + +const QString KexiDBConnectionData::caption() const { return m_data->caption; } +void KexiDBConnectionData::setCaption(const QString& name) { m_data->caption = name; } + +const QString KexiDBConnectionData::description() const { return m_data->description; } +void KexiDBConnectionData::setDescription(const QString& desc) { m_data->description = desc; } + +const QString KexiDBConnectionData::driverName() const { return m_data->driverName; } +void KexiDBConnectionData::setDriverName(const QString& driver) { m_data->driverName = driver; } + +bool KexiDBConnectionData::localSocketFileUsed() const { return m_data->useLocalSocketFile; } +void KexiDBConnectionData::setLocalSocketFileUsed(bool used) { m_data->useLocalSocketFile = used; } +const QString KexiDBConnectionData::localSocketFileName() const { return m_data->localSocketFileName; } +void KexiDBConnectionData::setLocalSocketFileName(const QString& socketfilename) { m_data->localSocketFileName = socketfilename; } + +const QString KexiDBConnectionData::databaseName() const { return m_dbname; } +void KexiDBConnectionData::setDatabaseName(const QString& dbname) { m_dbname = dbname; } + +const QString KexiDBConnectionData::hostName() const { return m_data->hostName; } +void KexiDBConnectionData::setHostName(const QString& hostname) { m_data->hostName = hostname; } + +int KexiDBConnectionData::port() const { return m_data->port; } +void KexiDBConnectionData::setPort(int p) { m_data->port = p; } + +const QString KexiDBConnectionData::password() const { return m_data->password; } +void KexiDBConnectionData::setPassword(const QString& passwd) { m_data->password = passwd; } + +const QString KexiDBConnectionData::userName() const { return m_data->userName; } +void KexiDBConnectionData::setUserName(const QString& username) { m_data->userName = username; } + +const QString KexiDBConnectionData::fileName() const { return m_data->fileName(); } +void KexiDBConnectionData::setFileName(const QString& filename) { m_data->setFileName(filename); } + +const QString KexiDBConnectionData::dbPath() const { return m_data->dbPath(); } +const QString KexiDBConnectionData::dbFileName() const { return m_data->dbFileName(); } +const QString KexiDBConnectionData::serverInfoString() const { return m_data->serverInfoString(true); } diff --git a/kexi/plugins/scripting/kexidb/kexidbconnectiondata.h b/kexi/plugins/scripting/kexidb/kexidbconnectiondata.h new file mode 100644 index 00000000..aaddffbd --- /dev/null +++ b/kexi/plugins/scripting/kexidb/kexidbconnectiondata.h @@ -0,0 +1,126 @@ +/*************************************************************************** + * kexidbconnectiondata.h + * This file is part of the KDE project + * copyright (C)2004-2005 by Sebastian Sauer (mail@dipe.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 KROSS_KEXIDB_KEXIDBCONNECTIONDATA_H +#define KROSS_KEXIDB_KEXIDBCONNECTIONDATA_H + +#include <qstring.h> + +#include <api/object.h> +#include <api/variant.h> +#include <api/list.h> +#include <api/class.h> + +#include <kexidb/connection.h> +#include <kexidb/connectiondata.h> + +namespace Kross { namespace KexiDB { + + /** + * A KexiDBConnectionData is used to store the details needed for + * a connection with a database. + */ + class KexiDBConnectionData : public Kross::Api::Class<KexiDBConnectionData> + { + friend class KexiDBDriverManager; + public: + KexiDBConnectionData(::KexiDB::ConnectionData* data); + virtual ~KexiDBConnectionData(); + operator ::KexiDB::ConnectionData& () { return *m_data; } + operator ::KexiDB::ConnectionData* () { return m_data; } + virtual const QString getClassName() const; + ::KexiDB::ConnectionData* data() { return m_data; } + + private: + + /** Return the connection name. */ + const QString caption() const; + /** Set the connection name. */ + void setCaption(const QString& name); + + /** Return the description. */ + const QString description() const; + /** Set the description. */ + void setDescription(const QString& desc); + + /** Return drivername. */ + const QString driverName() const; + /** Set the drivername. */ + void setDriverName(const QString& driver); + + /** Return true if a local socket file is used else false. */ + bool localSocketFileUsed() const; + /** Set if the local socket file should be used. */ + void setLocalSocketFileUsed(bool used); + /** Return the local socket filename. */ + const QString localSocketFileName() const; + /** Set the local socket filename. */ + void setLocalSocketFileName(const QString& socketfilename); + + // For serverbased drivers + + /** Return the database name. */ + const QString databaseName() const; + /** Set the database name. */ + void setDatabaseName(const QString& dbname); + + /** Return the hostname. */ + const QString hostName() const; + /** Set the hostname. */ + void setHostName(const QString& hostname); + + /** Return the port number. */ + int port() const; + /** Set the port number. */ + void setPort(int p); + + /** Return the password. */ + const QString password() const; + /** Set the password. */ + void setPassword(const QString& passwd); + + /** Return the username. */ + const QString userName() const; + /** Set the username. */ + void setUserName(const QString& username); + + // For filebased drivers + + /** Return the filename. */ + const QString fileName() const; + /** Set the filename. */ + void setFileName(const QString& filename); + + /** Return the database path. */ + const QString dbPath() const; + /** Return the database filename. */ + const QString dbFileName() const; + + /** Return a user-friendly string representation. */ + const QString serverInfoString() const; + + private: + ::KexiDB::ConnectionData* m_data; + QString m_dbname; + }; + +}} + +#endif + diff --git a/kexi/plugins/scripting/kexidb/kexidbcursor.cpp b/kexi/plugins/scripting/kexidb/kexidbcursor.cpp new file mode 100644 index 00000000..3bc1763d --- /dev/null +++ b/kexi/plugins/scripting/kexidb/kexidbcursor.cpp @@ -0,0 +1,139 @@ +/*************************************************************************** + * kexidbcursor.cpp + * This file is part of the KDE project + * copyright (C)2004-2005 by Sebastian Sauer (mail@dipe.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 "kexidbcursor.h" +#include "kexidbconnection.h" + +#include <kexidb/tableschema.h> +#include <kexidb/queryschema.h> + +#include <kdebug.h> + +using namespace Kross::KexiDB; + +KexiDBCursor::KexiDBCursor(::KexiDB::Cursor* cursor) + : Kross::Api::Class<KexiDBCursor>("KexiDBCursor") + , m_cursor(cursor) +{ + this->addFunction0<Kross::Api::Variant>("open", this, &KexiDBCursor::open ); + this->addFunction0<Kross::Api::Variant>("isOpened", this, &KexiDBCursor::isOpened ); + this->addFunction0<Kross::Api::Variant>("reopen", this, &KexiDBCursor::reopen ); + this->addFunction0<Kross::Api::Variant>("close", this, &KexiDBCursor::close ); + this->addFunction0<Kross::Api::Variant>("moveFirst", this, &KexiDBCursor::moveFirst ); + this->addFunction0<Kross::Api::Variant>("moveLast", this, &KexiDBCursor::moveLast ); + this->addFunction0<Kross::Api::Variant>("movePrev", this, &KexiDBCursor::movePrev ); + this->addFunction0<Kross::Api::Variant>("moveNext", this, &KexiDBCursor::moveNext ); + this->addFunction0<Kross::Api::Variant>("bof", this, &KexiDBCursor::bof ); + this->addFunction0<Kross::Api::Variant>("eof", this, &KexiDBCursor::eof ); + this->addFunction0<Kross::Api::Variant>("at", this, &KexiDBCursor::at ); + this->addFunction0<Kross::Api::Variant>("fieldCount", this, &KexiDBCursor::fieldCount ); + this->addFunction1<Kross::Api::Variant, Kross::Api::Variant>("value", this, &KexiDBCursor::value ); + this->addFunction2<Kross::Api::Variant, Kross::Api::Variant, Kross::Api::Variant>("setValue", this, &KexiDBCursor::setValue ); + this->addFunction0<Kross::Api::Variant>("save", this, &KexiDBCursor::save ); +} + +KexiDBCursor::~KexiDBCursor() +{ + ///@todo check ownership + //delete m_cursor; + + clearBuffers(); +} + +void KexiDBCursor::clearBuffers() +{ + QMap<Q_LLONG, Record*>::ConstIterator + it( m_modifiedrecords.constBegin() ), end( m_modifiedrecords.constEnd() ); + for( ; it != end; ++it) + delete it.data(); + m_modifiedrecords.clear(); +} + +const QString KexiDBCursor::getClassName() const +{ + return "Kross::KexiDB::KexiDBCursor"; +} + +bool KexiDBCursor::open() { return m_cursor->open(); } +bool KexiDBCursor::isOpened() { return m_cursor->isOpened(); } +bool KexiDBCursor::reopen() { return m_cursor->reopen(); } +bool KexiDBCursor::close() { return m_cursor->close(); } + +bool KexiDBCursor::moveFirst() { return m_cursor->moveFirst(); } +bool KexiDBCursor::moveLast() { return m_cursor->moveLast(); } +bool KexiDBCursor::movePrev() { return m_cursor->movePrev(); } +bool KexiDBCursor::moveNext() { return m_cursor->moveNext(); } + +bool KexiDBCursor::bof() { return m_cursor->bof(); } +bool KexiDBCursor::eof() { return m_cursor->eof(); } + +Q_LLONG KexiDBCursor::at() { return m_cursor->at(); } +uint KexiDBCursor::fieldCount() { return m_cursor->fieldCount(); } + +QVariant KexiDBCursor::value(uint index) +{ + return m_cursor->value(index); +} + +bool KexiDBCursor::setValue(uint index, QVariant value) +{ + ::KexiDB::QuerySchema* query = m_cursor->query(); + if(! query) { + kdDebug() << "Invalid query in KexiDBCursor::setValue index=" << index << " value=" << value << endl; + return false; + } + + ::KexiDB::QueryColumnInfo* column = query->fieldsExpanded().at(index); + if(! column) { + kdDebug() << "Invalid column in KexiDBCursor::setValue index=" << index << " value=" << value << endl; + return false; + } + + const Q_LLONG position = m_cursor->at(); + if(! m_modifiedrecords.contains(position)) + m_modifiedrecords.replace(position, new Record(m_cursor)); + m_modifiedrecords[position]->buffer->insert(*column, value); + return true; +} + +bool KexiDBCursor::save() +{ + if(m_modifiedrecords.count() < 1) + return true; + + //It is needed to close the cursor before we are able to update the rows + //since else the database could be locked (e.g. at the case of SQLite a + //KexiDB: Object ERROR: 6: SQLITE_LOCKED would prevent updating). + //Maybe it works fine with other drivers like MySQL or Postqre? + m_cursor->close(); + + bool ok = true; + QMap<Q_LLONG, Record*>::ConstIterator + it( m_modifiedrecords.constBegin() ), end( m_modifiedrecords.constEnd() ); + for( ; it != end; ++it) { + bool b = m_cursor->updateRow(it.data()->rowdata, * it.data()->buffer, m_cursor->isBuffered()); + if(ok) { + ok = b; + //break; + } + } + //m_cursor->close(); + clearBuffers(); + return ok; +} diff --git a/kexi/plugins/scripting/kexidb/kexidbcursor.h b/kexi/plugins/scripting/kexidb/kexidbcursor.h new file mode 100644 index 00000000..6e92a38e --- /dev/null +++ b/kexi/plugins/scripting/kexidb/kexidbcursor.h @@ -0,0 +1,159 @@ +/*************************************************************************** + * kexidbcursor.h + * This file is part of the KDE project + * copyright (C)2004-2005 by Sebastian Sauer (mail@dipe.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 KROSS_KEXIDB_KEXIDBCURSOR_H +#define KROSS_KEXIDB_KEXIDBCURSOR_H + +#include <qstring.h> + +#include <api/object.h> +#include <api/variant.h> +#include <api/list.h> +#include <api/class.h> + +#include <kexidb/cursor.h> +#include <kexidb/roweditbuffer.h> + +namespace Kross { namespace KexiDB { + + // Forward declaration. + class KexiDBConnection; + + /** + * The cursor provides a control structure for the successive traversal + * of records in a result set as returned e.g. by a query. + * + * Example (in Python) that shows how to iterate over the result of a query; + * @code + * # Once we have a KexiDBConnection object we are able to execute a query string and get a cursor as result. + * cursor = connection.executeQueryString("SELECT * from emp") + * # Let's check if the query was successfully. + * if not cursor: raise("Query failed") + * # Walk through all items in the table. + * while(not cursor.eof()): + * # Iterate over the fields the record has. + * for i in range( cursor.fieldCount() ): + * # Print some information. + * print "%s %s %s" % (cursor.at(), i, cursor.value(i)) + * # and move on to the next record. + * cursor.moveNext() + * @endcode + * + * Example (in Python) that shows how to use a cursor to strip + * all whitespaces at the beginning and the end from the values + * in a table; + * @code + * import krosskexidb + * drivermanager = krosskexidb.DriverManager() + * connectiondata = drivermanager.createConnectionDataByFile("/home/me/kexiprojectfile.kexi") + * driver = drivermanager.driver( connectiondata.driverName() ) + * connection = driver.createConnection(connectiondata) + * if not connection.connect(): raise "Failed to connect" + * if not connection.useDatabase( connectiondata.databaseName() ): + * if not connection.useDatabase( connectiondata.fileName() ): + * raise "Failed to use database" + * + * table = connection.tableSchema("emp") + * query = table.query() + * cursor = connection.executeQuerySchema(query) + * if not cursor: raise("Query failed") + * while(not cursor.eof()): + * for i in range( cursor.fieldCount() ): + * v = str( cursor.value(i) ) + * if v.startswith(' ') or v.endswith(' '): + * cursor.setValue(i, v.strip()) + * cursor.moveNext() + * if not cursor.save(): raise "Failed to save changes" + * @endcode + */ + class KexiDBCursor : public Kross::Api::Class<KexiDBCursor> + { + public: + KexiDBCursor(::KexiDB::Cursor* cursor); + virtual ~KexiDBCursor(); + virtual const QString getClassName() const; + + private: + + /** Opens the cursor. */ + bool open(); + /** Returns true if the cursor is opened else false. */ + bool isOpened(); + /** Closes and then opens again the same cursor. */ + bool reopen(); + /** Closes previously opened cursor. */ + bool close(); + + /** Moves current position to the first record and retrieves it. */ + bool moveFirst(); + /** Moves current position to the last record and retrieves it. */ + bool moveLast(); + /** Moves current position to the previous record and retrieves it. */ + bool movePrev(); + /** Moves current position to the next record and retrieves it. */ + bool moveNext(); + + /** Returns true if current position is before first record. */ + bool bof(); + /** Returns true if current position is after last record. */ + bool eof(); + + /** Returns current internal position of the cursor's query. Records + are numbered from 0; the value -1 means that the cursor does not + point to a valid record. */ + Q_LLONG at(); + /** Returns the number of fields available for this cursor. */ + uint fieldCount(); + /** Returns the value stored in the passed column number (counting from 0). */ + QVariant value(uint index); + /** Set the value for the field defined with index. The new value is buffered + and does not got written as long as save() is not called. */ + bool setValue(uint index, QVariant value); + + /** Save any changes done with setValue(). You should call this only once at + the end of all value/setValue iterations cause the cursor is closed once + the changes got saved successfully. */ + bool save(); + + private: + ::KexiDB::Cursor* m_cursor; + + class Record { + public: + ::KexiDB::RowData rowdata; + ::KexiDB::RowEditBuffer* buffer; + Record(::KexiDB::Cursor* cursor) + : buffer( new ::KexiDB::RowEditBuffer(true) ) + { + cursor->storeCurrentRow(rowdata); + } + ~Record() + { + delete buffer; + } + }; + QMap<Q_LLONG, Record*> m_modifiedrecords; + + void clearBuffers(); + }; + +}} + +#endif + diff --git a/kexi/plugins/scripting/kexidb/kexidbdriver.cpp b/kexi/plugins/scripting/kexidb/kexidbdriver.cpp new file mode 100644 index 00000000..f019b237 --- /dev/null +++ b/kexi/plugins/scripting/kexidb/kexidbdriver.cpp @@ -0,0 +1,70 @@ +/*************************************************************************** + * kexidbdriver.cpp + * This file is part of the KDE project + * copyright (C)2004-2005 by Sebastian Sauer (mail@dipe.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 "kexidbdriver.h" +#include "kexidbdrivermanager.h" + +#include <qvaluelist.h> +#include <qptrlist.h> +#include <kdebug.h> + +#include <kexidb/connection.h> + +using namespace Kross::KexiDB; + +KexiDBDriver::KexiDBDriver(::KexiDB::Driver* driver) + : Kross::Api::Class<KexiDBDriver>("KexiDBDriver") + , m_driver(driver) +{ + this->addFunction0<Kross::Api::Variant>("isValid", this, &KexiDBDriver::isValid ); + this->addFunction0<Kross::Api::Variant>("versionMajor", this, &KexiDBDriver::versionMajor ); + this->addFunction0<Kross::Api::Variant>("versionMinor", this, &KexiDBDriver::versionMinor ); + this->addFunction1<Kross::Api::Variant, Kross::Api::Variant>("escapeString", this, &KexiDBDriver::escapeString); + this->addFunction0<Kross::Api::Variant>("isFileDriver", this, &KexiDBDriver::isFileDriver ); + this->addFunction0<Kross::Api::Variant>("fileDBDriverMimeType", this, &KexiDBDriver::fileDBDriverMimeType ); + this->addFunction1<Kross::Api::Variant, Kross::Api::Variant>("isSystemObjectName", this, &KexiDBDriver::isSystemObjectName ); + this->addFunction1<Kross::Api::Variant, Kross::Api::Variant>("isSystemDatabaseName", this, &KexiDBDriver::isSystemDatabaseName ); + this->addFunction1<Kross::Api::Variant, Kross::Api::Variant>("isSystemFieldName", this, &KexiDBDriver::isSystemFieldName ); + this->addFunction2<Kross::Api::Variant, Kross::Api::Variant, Kross::Api::Variant> ("valueToSQL", this, &KexiDBDriver::valueToSQL ); + + this->addFunction1<KexiDBConnection, KexiDBConnectionData>("createConnection", this, &KexiDBDriver::createConnection); + this->addFunction0< Kross::Api::ListT< KexiDBConnection > >("connectionsList", this, &KexiDBDriver::connectionsList); +} + +KexiDBDriver::~KexiDBDriver() +{ +} + +const QString KexiDBDriver::getClassName() const +{ + return "Kross::KexiDB::KexiDBDriver"; +} + +bool KexiDBDriver::isValid() { return m_driver->isValid(); } +int KexiDBDriver::versionMajor() { return m_driver->version().major; } +int KexiDBDriver::versionMinor() { return m_driver->version().minor; } +QString KexiDBDriver::escapeString(const QString& s) { return m_driver->escapeString(s); } +bool KexiDBDriver::isFileDriver() { return m_driver->isFileDriver(); } +QString KexiDBDriver::fileDBDriverMimeType() { return m_driver->fileDBDriverMimeType(); } +bool KexiDBDriver::isSystemObjectName(const QString& name) { return m_driver->isSystemObjectName(name); } +bool KexiDBDriver::isSystemDatabaseName(const QString& name) { return m_driver->isSystemDatabaseName(name); } +bool KexiDBDriver::isSystemFieldName(const QString& name) { return m_driver->isSystemFieldName(name); } +QString KexiDBDriver::valueToSQL(const QString& fieldtype, const QVariant& value) { return m_driver->valueToSQL(fieldtype, value); } +KexiDBConnection* KexiDBDriver::createConnection(KexiDBConnectionData* data) { return new KexiDBConnection( m_driver->createConnection(*data) ); } +QPtrList< ::KexiDB::Connection > KexiDBDriver::connectionsList() { return m_driver->connectionsList(); } diff --git a/kexi/plugins/scripting/kexidb/kexidbdriver.h b/kexi/plugins/scripting/kexidb/kexidbdriver.h new file mode 100644 index 00000000..edf7283c --- /dev/null +++ b/kexi/plugins/scripting/kexidb/kexidbdriver.h @@ -0,0 +1,114 @@ +/*************************************************************************** + * kexidbdriver.h + * This file is part of the KDE project + * copyright (C)2004-2005 by Sebastian Sauer (mail@dipe.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 KROSS_KEXIDB_KEXIDBDRIVER_H +#define KROSS_KEXIDB_KEXIDBDRIVER_H + +#include <qstring.h> +#include <qvaluelist.h> +//#include <qguardedptr.h> + +#include <api/object.h> +#include <api/variant.h> +#include <api/list.h> +#include <api/class.h> + +#include <kexidb/driver.h> + +#include "kexidbconnection.h" +#include "kexidbconnectiondata.h" + +namespace Kross { namespace KexiDB { + + /** + * Drivers are the implementations Kexi uses to access the + * driver-backends. + * + * Example (in Python) ; + * @code + * # Import the kexidb module. + * import krosskexidb + * # Get the drivermanager. + * drivermanager = krosskexidb.DriverManager() + * # Create the driver now. + * driver = drivermanager.driver("SQLite3") + * # Check if the driver is valid. + * if not driver.isValid(): raise "Invalid driver" + * # Create a connectiondata object. + * connectiondata = drivermanager.createConnectionData() + * # Fill the new connectiondata object with what we need to connect. + * connectiondata.setFileName("/home/user/kexisqlite3file.kexi") + * # Print the list of connections before. + * print driver.connectionsList() + * # Create the connection now. + * connection = driver.createConnection(connectiondata) + * # Print the list of connections again. This includes our just created connection now. + * print driver.connectionsList() + * @endcode + */ + class KexiDBDriver : public Kross::Api::Class<KexiDBDriver> + { + public: + KexiDBDriver(::KexiDB::Driver* driver); + virtual ~KexiDBDriver(); + virtual const QString getClassName() const; + + private: + + /** Return true if this driver is valid else false. */ + bool isValid(); + /** The drivers major versionnumber. */ + int versionMajor(); + /** The drivers minor versionnumber. */ + int versionMinor(); + /** Driver-specific SQL string escaping. For example the " or ' char may + need to be escaped for values used within SQL-statements. */ + QString escapeString(const QString& s); + /** Returns true if this driver is file-based. */ + bool isFileDriver(); + /** Return a name of MIME type of files handled by this driver if it is a + file-based database's driver otherwise returns null string. */ + QString fileDBDriverMimeType(); + /** Returns true if the passed string is a system object's name, eg. name + of build-in system table that cannot be used or created by a user. */ + bool isSystemObjectName(const QString& name); + /** Returns true if the passed string is a system database's name, eg. name + of build-in, system database that cannot be used or created by a user. */ + bool isSystemDatabaseName(const QString& name); + /** Returns true if the passed string is a system field's name, build-in + system field that cannot be used or created by a user. */ + bool isSystemFieldName(const QString& name); + /** The as second argument passed string got escaped to be usable within + a SQL-statement and those escaped string got returned by the method. + The first argument defines the fieldtype to what we should escape the + second argument to. */ + QString valueToSQL(const QString& fieldtype, const QVariant& value); + /** Create a new KexiDBConnection object and return it. */ + KexiDBConnection* createConnection(KexiDBConnectionData* data); + /** Return a list of KexiDBConnection objects. */ + QPtrList< ::KexiDB::Connection > connectionsList(); + + private: + ::KexiDB::Driver* m_driver; + }; + +}} + +#endif + diff --git a/kexi/plugins/scripting/kexidb/kexidbdrivermanager.cpp b/kexi/plugins/scripting/kexidb/kexidbdrivermanager.cpp new file mode 100644 index 00000000..66a0df26 --- /dev/null +++ b/kexi/plugins/scripting/kexidb/kexidbdrivermanager.cpp @@ -0,0 +1,178 @@ +/*************************************************************************** + * kexidbdrivermanager.cpp + * This file is part of the KDE project + * copyright (C)2004-2005 by Sebastian Sauer (mail@dipe.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 "kexidbdrivermanager.h" +#include "kexidbdriver.h" +#include "kexidbconnectiondata.h" +#include "kexidbfield.h" +#include "kexidbschema.h" + +#include <api/exception.h> + +#include <qguardedptr.h> +#include <kdebug.h> +#include <kmimetype.h> + +#include <kexidb/driver.h> +#include <kexidb/connectiondata.h> +#include <kexidb/field.h> +#include <kexidb/tableschema.h> +#include <kexidb/queryschema.h> + +using namespace Kross::KexiDB; + +KexiDBDriverManager::KexiDBDriverManager() + : Kross::Api::Class<KexiDBDriverManager>("DriverManager") +{ + //krossdebug( QString("Kross::KexiDB::KexiDBDriverManager::KexiDBDriverManager()") ); + + this->addFunction0< Kross::Api::Variant >("driverNames", this, &KexiDBDriverManager::driverNames); + + this->addFunction1< KexiDBDriver, Kross::Api::Variant >("driver", this, &KexiDBDriverManager::driver); + this->addFunction1< Kross::Api::Variant, Kross::Api::Variant >("lookupByMime", this, &KexiDBDriverManager::lookupByMime); + this->addFunction1< Kross::Api::Variant, Kross::Api::Variant >("mimeForFile", this, &KexiDBDriverManager::mimeForFile); + + this->addFunction0< KexiDBConnectionData >("createConnectionData", this, &KexiDBDriverManager::createConnectionData); + this->addFunction1< KexiDBConnectionData, Kross::Api::Variant >("createConnectionDataByFile", this, &KexiDBDriverManager::createConnectionDataByFile); + this->addFunction0< KexiDBField >("field", this, &KexiDBDriverManager::field); + this->addFunction1< KexiDBTableSchema, Kross::Api::Variant >("tableSchema", this, &KexiDBDriverManager::tableSchema); + this->addFunction0< KexiDBQuerySchema>("querySchema", this, &KexiDBDriverManager::querySchema); +} + +KexiDBDriverManager::~KexiDBDriverManager() { + //krossdebug( QString("Kross::KexiDB::KexiDBDriverManager::~KexiDBDriverManager()") ); +} + +const QString KexiDBDriverManager::getClassName() const { + return "Kross::KexiDB::KexiDBDriverManager"; +} + +KexiDB::DriverManager& KexiDBDriverManager::driverManager() +{ + if(m_drivermanager.error()) + throw Kross::Api::Exception::Ptr( new Kross::Api::Exception(QString("KexiDB::DriverManager error: %1").arg(m_drivermanager.errorMsg())) ); + return m_drivermanager; +} + +const QStringList KexiDBDriverManager::driverNames() { + return driverManager().driverNames(); +} + +KexiDBDriver* KexiDBDriverManager::driver(const QString& drivername) { + QGuardedPtr< ::KexiDB::Driver > driver = driverManager().driver(drivername); // caching is done by the DriverManager + if(! driver) return 0; + if(driver->error()) throw Kross::Api::Exception::Ptr( new Kross::Api::Exception(QString("KexiDB::Driver error for drivername '%1': %2").arg(drivername).arg(driver->errorMsg())) ); + return new KexiDBDriver(driver); +} + +const QString KexiDBDriverManager::lookupByMime(const QString& mimetype) { + return driverManager().lookupByMime(mimetype); +} + +const QString KexiDBDriverManager::mimeForFile(const QString& filename) { + QString mimename = KMimeType::findByFileContent( filename )->name(); + if(mimename.isEmpty() || mimename=="application/octet-stream" || mimename=="text/plain") + mimename = KMimeType::findByURL(filename)->name(); + return mimename; +} + +KexiDBConnectionData* KexiDBDriverManager::createConnectionData() { + return new KexiDBConnectionData( new ::KexiDB::ConnectionData() ); +} + +KexiDBConnectionData* KexiDBDriverManager::createConnectionDataByFile(const QString& filename) { + //! @todo reuse the original code! + + QString mimename = KMimeType::findByFileContent(filename)->name(); + if(mimename.isEmpty() || mimename=="application/octet-stream" || mimename=="text/plain") + mimename = KMimeType::findByURL(filename)->name(); + + if(mimename == "application/x-kexiproject-shortcut" || mimename == "application/x-kexi-connectiondata") { + KConfig config(filename, true, false); + QString groupkey; + QStringList groups(config.groupList()); + QStringList::ConstIterator it, end( groups.constEnd() ); + for( it = groups.constBegin(); it != end; ++it) { + if((*it).lower()!="file information") { + groupkey = *it; + break; + } + } + if(groupkey.isNull()) { + kdDebug() << "No groupkey in KexiDBDriverManager::createConnectionDataByFile filename=" << filename << endl; + return 0; + } + + config.setGroup(groupkey); + //QString type( config.readEntry("type", "database").lower() ); + //bool isDatabaseShortcut = (type == "database"); + + ::KexiDB::ConnectionData* data = new ::KexiDB::ConnectionData(); + int version = config.readNumEntry("version", 2); //KexiDBShortcutFile_version + data->setFileName(QString::null); + data->caption = config.readEntry("caption"); + data->description = config.readEntry("comment"); + QString dbname = config.readEntry("name"); + data->driverName = config.readEntry("engine"); + data->hostName = config.readEntry("server"); + data->port = config.readNumEntry("port", 0); + data->useLocalSocketFile = config.readBoolEntry("useLocalSocketFile", false); + data->localSocketFileName = config.readEntry("localSocketFile"); + + if(version >= 2 && config.hasKey("encryptedPassword")) { + data->password = config.readEntry("encryptedPassword"); + uint len = data->password.length(); + for (uint i=0; i<len; i++) + data->password[i] = QChar( data->password[i].unicode() - 47 - i ); + } + if(data->password.isEmpty()) + data->password = config.readEntry("password"); + + data->savePassword = ! data->password.isEmpty(); + data->userName = config.readEntry("user"); + + KexiDBConnectionData* c = new KexiDBConnectionData(data); + c->setDatabaseName(dbname); + return c; + } + + QString const drivername = driverManager().lookupByMime(mimename); + if(! drivername) { + kdDebug() << "No driver in KexiDBDriverManager::createConnectionDataByFile filename=" << filename << " mimename=" << mimename << endl; + return 0; + } + + ::KexiDB::ConnectionData* data = new ::KexiDB::ConnectionData(); + data->setFileName(filename); + data->driverName = drivername; + return new KexiDBConnectionData(data); +} + +KexiDBField* KexiDBDriverManager::field() { + return new KexiDBField( new ::KexiDB::Field() ); +} + +KexiDBTableSchema* KexiDBDriverManager::tableSchema(const QString& tablename) { + return new KexiDBTableSchema( new ::KexiDB::TableSchema(tablename) ); +} + +KexiDBQuerySchema* KexiDBDriverManager::querySchema() { + return new KexiDBQuerySchema( new ::KexiDB::QuerySchema() ); +} + diff --git a/kexi/plugins/scripting/kexidb/kexidbdrivermanager.h b/kexi/plugins/scripting/kexidb/kexidbdrivermanager.h new file mode 100644 index 00000000..b6e31108 --- /dev/null +++ b/kexi/plugins/scripting/kexidb/kexidbdrivermanager.h @@ -0,0 +1,105 @@ +/*************************************************************************** + * kexidbdrivermanager.h + * This file is part of the KDE project + * copyright (C)2004-2005 by Sebastian Sauer (mail@dipe.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 KROSS_KEXIDB_KEXIDBDRIVERMANAGER_H +#define KROSS_KEXIDB_KEXIDBDRIVERMANAGER_H + +#include <qstring.h> + +#include <api/object.h> +#include <api/variant.h> +#include <api/list.h> +#include <api/class.h> + +#include <kexidb/drivermanager.h> + +namespace Kross { namespace KexiDB { + + // Forward declarations. + class KexiDBDriver; + class KexiDBConnectionData; + class KexiDBField; + class KexiDBTableSchema; + class KexiDBQuerySchema; + + /** + * The drivermanager is the base class to access KexiDBDriver objects and provides + * common functionality to deal with the KexiDB module. + * + * Example (in Python) ; + * @code + * # Import the kexidb module. + * import krosskexidb + * # Get the drivermanager. + * drivermanager = krosskexidb.DriverManager() + * # Let's determinate the mimetype (e.g. "application/x-sqlite3"). + * mimetype = drivermanager.mimeForFile("/home/user/mykexidbfile.kexi") + * # Now we use that mimetype to get the name of the driver to handle that file (e.g. "SQLite3") + * drivername = drivermanager.lookupByMime(mimetype) + * # We are able to create the driver now. + * driver = drivermanager.driver(drivername) + * @endcode + */ + class KexiDBDriverManager : public Kross::Api::Class<KexiDBDriverManager> + { + public: + KexiDBDriverManager(); + virtual ~KexiDBDriverManager(); + virtual const QString getClassName() const; + + private: + + /** Returns a list with avaible drivernames. */ + const QStringList driverNames(); + + /** Return the to the defined drivername matching KexiDBDriver object. */ + KexiDBDriver* driver(const QString& drivername); + + /** Return the to the defined mimetype-string matching drivername. */ + const QString lookupByMime(const QString& mimetype); + + /** Return the matching mimetype for the defined file. */ + const QString mimeForFile(const QString& filename); + + /** Return a new KexiDBConnectionData object. */ + KexiDBConnectionData* createConnectionData(); + + /** Create and return a KexiDBConnectionData object. Fill the content of the + KexiDBConnectionData object with the defined file as. The file could be e.g. + a *.kexi file or a *.kexis file. */ + KexiDBConnectionData* createConnectionDataByFile(const QString& filename); + + /** Return a new KexiDBField object. */ + KexiDBField* field(); + + /** Return a new KexiDBTableSchema object. */ + KexiDBTableSchema* tableSchema(const QString& tablename); + + /** Return a new KexiDBQuerySchema object. */ + KexiDBQuerySchema* querySchema(); + + private: + inline ::KexiDB::DriverManager& driverManager(); + ::KexiDB::DriverManager m_drivermanager; + }; + +}} + +#endif + diff --git a/kexi/plugins/scripting/kexidb/kexidbfield.cpp b/kexi/plugins/scripting/kexidb/kexidbfield.cpp new file mode 100644 index 00000000..949b5e1a --- /dev/null +++ b/kexi/plugins/scripting/kexidb/kexidbfield.cpp @@ -0,0 +1,147 @@ +/*************************************************************************** + * kexidbfield.cpp + * This file is part of the KDE project + * copyright (C)2004-2005 by Sebastian Sauer (mail@dipe.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 "kexidbfield.h" + +#include <api/variant.h> + +using namespace Kross::KexiDB; + +KexiDBField::KexiDBField(::KexiDB::Field* field) + : Kross::Api::Class<KexiDBField>("KexiDBField") + , m_field(field) +{ + this->addFunction0< Kross::Api::Variant >("type", this, &KexiDBField::type); + this->addFunction1< void, Kross::Api::Variant >("setType", this, &KexiDBField::setType); + + this->addFunction0< Kross::Api::Variant >("subType", this, &KexiDBField::subType); + this->addFunction1< void, Kross::Api::Variant >("setSubType", this, &KexiDBField::setSubType); + + this->addFunction0< Kross::Api::Variant >("variantType", this, &KexiDBField::variantType); + this->addFunction0< Kross::Api::Variant >("typeGroup", this, &KexiDBField::typeGroup); + + this->addFunction0< Kross::Api::Variant >("isAutoInc", this, &KexiDBField::isAutoInc); + this->addFunction1< void, Kross::Api::Variant >("setAutoInc", this, &KexiDBField::setAutoInc); + + this->addFunction0< Kross::Api::Variant >("isUniqueKey", this, &KexiDBField::isUniqueKey); + this->addFunction1< void, Kross::Api::Variant >("setUniqueKey", this, &KexiDBField::setUniqueKey); + + this->addFunction0< Kross::Api::Variant >("isPrimaryKey", this, &KexiDBField::isPrimaryKey); + this->addFunction1< void, Kross::Api::Variant >("setPrimaryKey", this, &KexiDBField::setPrimaryKey); + + this->addFunction0< Kross::Api::Variant >("isForeignKey", this, &KexiDBField::isForeignKey); + this->addFunction1< void, Kross::Api::Variant >("setForeignKey", this, &KexiDBField::setForeignKey); + + this->addFunction0< Kross::Api::Variant >("isNotNull", this, &KexiDBField::isNotNull); + this->addFunction1< void, Kross::Api::Variant >("setNotNull", this, &KexiDBField::setNotNull); + + this->addFunction0< Kross::Api::Variant >("isNotEmpty", this, &KexiDBField::isNotEmpty); + this->addFunction1< void, Kross::Api::Variant >("setNotEmpty", this, &KexiDBField::setNotEmpty); + + this->addFunction0< Kross::Api::Variant >("isIndexed", this, &KexiDBField::isIndexed); + this->addFunction1< void, Kross::Api::Variant >("setIndexed", this, &KexiDBField::setIndexed); + + this->addFunction0< Kross::Api::Variant >("isUnsigned", this, &KexiDBField::isUnsigned); + this->addFunction1< void, Kross::Api::Variant >("setUnsigned", this, &KexiDBField::setUnsigned); + + this->addFunction0< Kross::Api::Variant >("name", this, &KexiDBField::name); + this->addFunction1< void, Kross::Api::Variant >("setName", this, &KexiDBField::setName); + + this->addFunction0< Kross::Api::Variant >("caption", this, &KexiDBField::caption); + this->addFunction1< void, Kross::Api::Variant >("setCaption", this, &KexiDBField::setCaption); + + this->addFunction0< Kross::Api::Variant >("description", this, &KexiDBField::description); + this->addFunction1< void, Kross::Api::Variant >("setDescription", this, &KexiDBField::setDescription); + + this->addFunction0< Kross::Api::Variant >("length", this, &KexiDBField::length); + this->addFunction1< void, Kross::Api::Variant >("setLength", this, &KexiDBField::setLength); + + this->addFunction0< Kross::Api::Variant >("precision", this, &KexiDBField::precision); + this->addFunction1< void, Kross::Api::Variant >("setPrecision", this, &KexiDBField::setPrecision); + + this->addFunction0< Kross::Api::Variant >("width", this, &KexiDBField::width); + this->addFunction1< void, Kross::Api::Variant >("setWidth", this, &KexiDBField::setWidth); + + this->addFunction0< Kross::Api::Variant >("defaultValue", this, &KexiDBField::defaultValue); + this->addFunction1< void, Kross::Api::Variant >("setDefaultValue", this, &KexiDBField::setDefaultValue); +} + +KexiDBField::~KexiDBField() +{ +} + +const QString KexiDBField::getClassName() const +{ + return "Kross::KexiDB::KexiDBField"; +} + +const QString KexiDBField::type() { return m_field->typeString(); } +void KexiDBField::setType(const QString type) { m_field->setType( ::KexiDB::Field::typeForString(type) ); } + +const QString KexiDBField::subType() { return m_field->subType(); } +void KexiDBField::setSubType(const QString& subtype) { m_field->setSubType(subtype); } + +const QString KexiDBField::variantType() { return QVariant::typeToName( m_field->variantType() ); } +const QString KexiDBField::typeGroup() { return m_field->typeGroupString(); } + +bool KexiDBField::isAutoInc() { return m_field->isAutoIncrement(); } +void KexiDBField::setAutoInc(bool autoinc) { m_field->setAutoIncrement(autoinc); } + +bool KexiDBField::isUniqueKey() { return m_field->isUniqueKey(); } +void KexiDBField::setUniqueKey(bool unique) { m_field->setUniqueKey(unique); } + +bool KexiDBField::isPrimaryKey() { return m_field->isPrimaryKey(); } +void KexiDBField::setPrimaryKey(bool primary) { m_field->setPrimaryKey(primary); } + +bool KexiDBField::isForeignKey() { return m_field->isForeignKey(); } +void KexiDBField::setForeignKey(bool foreign) { m_field->setForeignKey(foreign); } + +bool KexiDBField::isNotNull() { return m_field->isNotNull(); } +void KexiDBField::setNotNull(bool notnull) { m_field->setNotNull(notnull); } + +bool KexiDBField::isNotEmpty() { return m_field->isNotEmpty(); } +void KexiDBField::setNotEmpty(bool notempty) { m_field->setNotEmpty(notempty); } + +bool KexiDBField::isIndexed() { return m_field->isIndexed(); } +void KexiDBField::setIndexed(bool indexed) { m_field->setIndexed(indexed); } + +bool KexiDBField::isUnsigned() { return m_field->isUnsigned(); } +void KexiDBField::setUnsigned(bool isunsigned) { m_field->setUnsigned(isunsigned); } + +const QString KexiDBField::name() { return m_field->name(); } +void KexiDBField::setName(const QString& name) { m_field->setName(name); } + +const QString KexiDBField::caption() { return m_field->caption(); } +void KexiDBField::setCaption(const QString& caption) { m_field->setCaption(caption); } + +const QString KexiDBField::description() { return m_field->description(); } +void KexiDBField::setDescription(const QString& desc) { m_field->setDescription(desc); } + +uint KexiDBField::length() { return m_field->length(); } +void KexiDBField::setLength(uint length) { m_field->setLength(length); } + +uint KexiDBField::precision() { return m_field->precision(); } +void KexiDBField::setPrecision(uint precision) { m_field->setPrecision(precision); } + +uint KexiDBField::width() { return m_field->width(); } +void KexiDBField::setWidth(uint width) { m_field->setWidth(width); } + +QVariant KexiDBField::defaultValue() { return m_field->defaultValue(); } +void KexiDBField::setDefaultValue(const QVariant& defaultvalue) { m_field->setDefaultValue(defaultvalue); } diff --git a/kexi/plugins/scripting/kexidb/kexidbfield.h b/kexi/plugins/scripting/kexidb/kexidbfield.h new file mode 100644 index 00000000..a4c2ef23 --- /dev/null +++ b/kexi/plugins/scripting/kexidb/kexidbfield.h @@ -0,0 +1,148 @@ +/*************************************************************************** + * kexidbfield.h + * This file is part of the KDE project + * copyright (C)2004-2005 by Sebastian Sauer (mail@dipe.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 KROSS_KEXIDB_KEXIDBFIELD_H +#define KROSS_KEXIDB_KEXIDBFIELD_H + +#include <qstring.h> + +#include <api/object.h> +#include <api/list.h> +#include <api/class.h> + +#include <kexidb/drivermanager.h> +#include <kexidb/field.h> + +namespace Kross { namespace KexiDB { + + /** + * A field in a record. + */ + class KexiDBField : public Kross::Api::Class<KexiDBField> + { + public: + KexiDBField(::KexiDB::Field* field); + virtual ~KexiDBField(); + virtual const QString getClassName() const; + ::KexiDB::Field* field() { return m_field; } + + private: + + /** Returns the type string for this field, e.g. "Integer" for Integer type. */ + const QString type(); + /** Sets the type string for this field, e.g. "Integer" for Integer type. */ + void setType(const QString type); + + /** Returns the optional subtype for this field. Subtype is a string providing + additional hint for field's type. E.g. for BLOB type, it can be a MIME type or + certain QVariant type name, for example: "QPixmap", "QColor" or "QFont". */ + const QString subType(); + /** Sets the optional subtype for this field. */ + void setSubType(const QString& subtype); + + /** Returns the QVariant::typeName which is equivalent to the type this field has. */ + const QString variantType(); + /** Returns type group string for this field, e.g. "IntegerGroup" for IntegerGroup type. */ + const QString typeGroup(); + + /** Returns true if the field is autoincrement (e.g. integer/numeric). */ + bool isAutoInc(); + /** Sets auto increment flag. */ + void setAutoInc(bool autoinc); + + /** Returns true if the field is member of single-field unique key. */ + bool isUniqueKey(); + /** Specifies whether the field has single-field unique constraint or not. */ + void setUniqueKey(bool unique); + + /** Returns true if the field is member of single-field primary key. */ + bool isPrimaryKey(); + /** Specifies whether the field is single-field primary key or not. */ + void setPrimaryKey(bool primary); + + /** Returns true if the field is member of single-field foreign key. */ + bool isForeignKey(); + /** Sets whether the field has to be declared with single-field foreign key. */ + void setForeignKey(bool foreign); + + /** Returns true if the field is not allowed to be null. */ + bool isNotNull(); + /** Specifies whether the field has single-field unique constraint or not. */ + void setNotNull(bool notnull); + + /** Returns true if the field is not allowed to be empty. */ + bool isNotEmpty(); + /** Specifies whether the field has single-field unique constraint or not. */ + void setNotEmpty(bool notempty); + + /** Returns true if the field is indexed using single-field database index. */ + bool isIndexed(); + /** Specifies whether the field is indexed or not. */ + void setIndexed(bool indexed); + + /** Returns true if the field is an unsigned integer. */ + bool isUnsigned(); + /** Specifies whether the field is an unsigned integer or not. */ + void setUnsigned(bool isunsigned); + + /** Returns the name of this field. */ + const QString name(); + /** Sets the name of this field. */ + void setName(const QString& name); + + /** Returns the caption of this field. */ + const QString caption(); + /** Sets the caption of this field. */ + void setCaption(const QString& caption); + + /** Returns the descriptive text for this field. */ + const QString description(); + /** Set the description for this field. */ + void setDescription(const QString& desc); + + /** Returns the length of text if the field type is text. */ + uint length(); + /** Sets the length for this field. Only works for Text Type (not including LongText). */ + void setLength(uint length); + + /** Returns precision for numeric and other fields that have both length and + precision (floating point types). */ + uint precision(); + /** Sets the precision for numeric and other fields. */ + void setPrecision(uint precision); + + /** Returns the width of this field (usually in pixels or points). + 0 (the default) means there is no hint for the width. */ + uint width(); + /** Sets the width of this field. */ + void setWidth(uint width); + + /** Returns the default value this field has. */ + QVariant defaultValue(); + /** Sets the default value this field has. */ + void setDefaultValue(const QVariant& defaultvalue); + + private: + ::KexiDB::Field* m_field; + }; + +}} + +#endif + diff --git a/kexi/plugins/scripting/kexidb/kexidbfieldlist.cpp b/kexi/plugins/scripting/kexidb/kexidbfieldlist.cpp new file mode 100644 index 00000000..f36bf0b0 --- /dev/null +++ b/kexi/plugins/scripting/kexidb/kexidbfieldlist.cpp @@ -0,0 +1,100 @@ +/*************************************************************************** + * kexidbfieldlist.cpp + * This file is part of the KDE project + * copyright (C)2004-2005 by Sebastian Sauer (mail@dipe.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 "kexidbfieldlist.h" +#include "kexidbfield.h" + +#include <api/variant.h> +#include <api/exception.h> + +#include <kdebug.h> + +using namespace Kross::KexiDB; + +KexiDBFieldList::KexiDBFieldList(::KexiDB::FieldList* fieldlist) + : Kross::Api::Class<KexiDBFieldList>("KexiDBFieldList") + , m_fieldlist(fieldlist) +{ + this->addFunction0< Kross::Api::Variant >("fieldCount", this, &KexiDBFieldList::fieldCount); + this->addFunction1< KexiDBField, Kross::Api::Variant >("field", this, &KexiDBFieldList::field); + this->addFunction1< KexiDBField, Kross::Api::Variant >("fieldByName", this, &KexiDBFieldList::fieldByName); + + this->addFunction0< Kross::Api::List >("fields", this, &KexiDBFieldList::fields); + + this->addFunction1< Kross::Api::Variant, KexiDBField >("hasField", this, &KexiDBFieldList::hasField); + this->addFunction0< Kross::Api::Variant >("names", this, &KexiDBFieldList::names); + + this->addFunction1< void, KexiDBField >("addField", this, &KexiDBFieldList::addField); + this->addFunction2< void, Kross::Api::Variant, KexiDBField >("insertField", this, &KexiDBFieldList::insertField); + this->addFunction1< void, KexiDBField >("removeField", this, &KexiDBFieldList::removeField); + this->addFunction0< void >("clear", this, &KexiDBFieldList::clear); + this->addFunction1< void, KexiDBFieldList >("setFields", this, &KexiDBFieldList::setFields); + + this->addFunction1< KexiDBFieldList, Kross::Api::Variant >("subList", this, &KexiDBFieldList::subList); +} + +KexiDBFieldList::~KexiDBFieldList() +{ +} + +const QString KexiDBFieldList::getClassName() const +{ + return "Kross::KexiDB::KexiDBFieldList"; +} + +uint KexiDBFieldList::fieldCount() { + return m_fieldlist->fieldCount(); +} + +KexiDBField* KexiDBFieldList::field(uint index) { + ::KexiDB::Field* field = m_fieldlist->field(index); + return field ? new KexiDBField(field) : 0; +} + +KexiDBField* KexiDBFieldList::fieldByName(const QString& name) { + ::KexiDB::Field* field = m_fieldlist->field(name); + return field ? new KexiDBField(field) : 0; +} + +Kross::Api::List* KexiDBFieldList::fields() { + return new Kross::Api::ListT<KexiDBField>( *m_fieldlist->fields() ); +} + +bool KexiDBFieldList::hasField(KexiDBField* field) { return m_fieldlist->hasField( field->field() ); } +const QStringList KexiDBFieldList::names() const { return m_fieldlist->names(); } +void KexiDBFieldList::addField(KexiDBField* field) { m_fieldlist->addField( field->field() ); } +void KexiDBFieldList::insertField(uint index, KexiDBField* field) { m_fieldlist->insertField(index, field->field()); } +void KexiDBFieldList::removeField(KexiDBField* field) { m_fieldlist->removeField( field->field() ); } +void KexiDBFieldList::clear() { m_fieldlist->clear(); } + +void KexiDBFieldList::setFields(KexiDBFieldList* fieldlist) { + m_fieldlist->clear(); + ::KexiDB::FieldList* fl = fieldlist->fieldlist(); + for(::KexiDB::Field::ListIterator it = *fl->fields(); it.current(); ++it) + m_fieldlist->addField( it.current() ); +} + +KexiDBFieldList* KexiDBFieldList::subList(QValueList<QVariant> list) { + QValueList<QVariant>::ConstIterator it( list.constBegin() ), end( list.constEnd() ); + QStringList sl; + for(; it != end; ++it) sl.append( (*it).toString() ); + ::KexiDB::FieldList* fl = m_fieldlist->subList(sl); + return fl ? new Kross::KexiDB::KexiDBFieldList(fl) : 0; +} + diff --git a/kexi/plugins/scripting/kexidb/kexidbfieldlist.h b/kexi/plugins/scripting/kexidb/kexidbfieldlist.h new file mode 100644 index 00000000..ee990eb3 --- /dev/null +++ b/kexi/plugins/scripting/kexidb/kexidbfieldlist.h @@ -0,0 +1,104 @@ +/*************************************************************************** + * kexidbfieldlist.h + * This file is part of the KDE project + * copyright (C)2004-2005 by Sebastian Sauer (mail@dipe.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 KROSS_KEXIDB_KEXIDBFIELDLIST_H +#define KROSS_KEXIDB_KEXIDBFIELDLIST_H + +#include <qstring.h> + +#include <api/object.h> +#include <api/list.h> +#include <api/class.h> + +#include <kexidb/drivermanager.h> +#include <kexidb/fieldlist.h> + +namespace Kross { namespace KexiDB { + + // Forward declarations. + class KexiDBField; + class KexiDBFieldList; + + /** + * A list of fields. The KexiDBFieldList can be used to handle KexiDBField objects + * in a backend-independent way. + * + * Example (in Python) ; + * @code + * # Get the tableschema for the "dept" table. + * table = connection.tableSchema("dept") + * # Create a KexiDBFieldList based on the table and filled with the selected fields. + * subfields = ["deptno","name","loc"] + * fieldlist = table.fieldlist().subList(subfields) + * # Create the "SELECT * from dept;" queryschema. + * query = table.query() + * # We change the queryschema to "SELECT deptno,name,loc FROM dept;" now. + * query.fieldlist().setFields(fieldlist) + * # and change the query to "SELECT deptno,name,loc FROM dept WHERE deptno=5;" + * query.setWhereExpression("deptno=5") + * # Execute the query and get a KexiDBCursor object as result which could be used to iterate through the result. + * cursor = connection.executeQuerySchema(query) + * @endcode + */ + class KexiDBFieldList : public Kross::Api::Class<KexiDBFieldList> + { + public: + KexiDBFieldList(::KexiDB::FieldList* fieldlist); + virtual ~KexiDBFieldList(); + virtual const QString getClassName() const; + ::KexiDB::FieldList* fieldlist() { return m_fieldlist; } + + private: + + /** Returns the number of fields. */ + uint fieldCount(); + /** Return the field specified by the index-number passed as an argument. */ + KexiDBField* field(uint index); + /** Return the field specified by the as an argument passed fieldname. */ + KexiDBField* fieldByName(const QString& name); + + /** Returns a list of all fields. */ + Kross::Api::List* fields(); + /** Returns true if the KexiDBField object passed as an argument is in the field list. */ + bool hasField(KexiDBField* field); + /** Return a list of field names. */ + const QStringList names() const; + + /** Adds the KexiDBField object passed as an argument to the field list. */ + void addField(KexiDBField* field); + /** Inserts the KexiDBField object passed as the second argument + into the field list at the position defined by the first argument. */ + void insertField(uint index, KexiDBField* field); + /** Removes the KexiDBField object passed as an argument from the field list. */ + void removeField(KexiDBField* field); + /** Removes all KexiDBField objects from the fieldlist. */ + void clear(); + /** Set the fieldlist to the as argument passed list of fields. */ + void setFields(KexiDBFieldList* fieldlist); + /** Creates and returns list that contain fields selected by name. */ + KexiDBFieldList* subList(QValueList<QVariant> list); + + private: + ::KexiDB::FieldList* m_fieldlist; + }; + +}} + +#endif + diff --git a/kexi/plugins/scripting/kexidb/kexidbmodule.cpp b/kexi/plugins/scripting/kexidb/kexidbmodule.cpp new file mode 100644 index 00000000..36f7b71f --- /dev/null +++ b/kexi/plugins/scripting/kexidb/kexidbmodule.cpp @@ -0,0 +1,74 @@ +/*************************************************************************** + * kexidbmodule.cpp + * This file is part of the KDE project + * copyright (C)2004-2005 by Sebastian Sauer (mail@dipe.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 "kexidbmodule.h" +#include "kexidbdrivermanager.h" +#include "kexidbconnection.h" + +//#include <api/object.h> +//#include <api/variant.h> +#include <main/manager.h> + +#include <kdebug.h> + +// The as version() published versionnumber of this kross-module. +#define KROSS_KEXIDB_VERSION 1 + +extern "C" +{ + /** + * Exported an loadable function as entry point to use + * the \a KexiDBModule. + */ + Kross::Api::Object* KDE_EXPORT init_module(Kross::Api::Manager* manager) + { + return new Kross::KexiDB::KexiDBModule(manager); + } +} + +using namespace Kross::KexiDB; + +KexiDBModule::KexiDBModule(Kross::Api::Manager* /*manager*/) + : Kross::Api::Module("KexiDB") + //, m_manager(manager) +{ + //kdDebug() << "Kross::KexiDB::KexiDBModule Ctor" << endl; + addChild( "version", new Kross::Api::Variant(KROSS_KEXIDB_VERSION) ); + addChild( new KexiDBDriverManager() ); +} + +KexiDBModule::~KexiDBModule() +{ + //kdDebug() << "Kross::KexiDB::KexiDBModule Dtor" << endl; +} + +const QString KexiDBModule::getClassName() const +{ + return "Kross::KexiDB::KexiDBModule"; +} + +Kross::Api::Object::Ptr KexiDBModule::get(const QString& name, void* p) +{ + if(name == "KexiDBConnection") { + ::KexiDB::Connection* connection = (::KexiDB::Connection*)p; + if(connection) + return new KexiDBConnection(connection); + } + return 0; +} diff --git a/kexi/plugins/scripting/kexidb/kexidbmodule.h b/kexi/plugins/scripting/kexidb/kexidbmodule.h new file mode 100644 index 00000000..b91b6047 --- /dev/null +++ b/kexi/plugins/scripting/kexidb/kexidbmodule.h @@ -0,0 +1,69 @@ +/*************************************************************************** + * kexidbmodule.h + * This file is part of the KDE project + * copyright (C)2004-2005 by Sebastian Sauer (mail@dipe.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 KROSS_KEXIDB_KEXIDBMODULE_H +#define KROSS_KEXIDB_KEXIDBMODULE_H + +#include <qstring.h> +#include <qvariant.h> + +#include <api/module.h> + +namespace Kross { namespace Api { + class Manager; +}} + +namespace Kross { + +/** + * KrossKexiDB provides access to the KexiDB database functionality. + */ +namespace KexiDB { + + /** + * \internal + * The KexiDBModule is the implementation of a kross-module. + */ + class KexiDBModule : public Kross::Api::Module + { + public: + KexiDBModule(Kross::Api::Manager* manager); + virtual ~KexiDBModule(); + virtual const QString getClassName() const; + + /** + * \internal + * Variable module-method use to call transparent some functionality + * the module provides. + * + * \param name A name passed to the method. This name is used internaly + * to determinate what the caller likes to do. Each implemented + * module have to implement what should be done. + * \param p A variable pointer passed to the method. It depends on + * the module and the name what this pointer is. + * \return a \a Kross::Api::Object or NULL. + */ + virtual Kross::Api::Object::Ptr get(const QString& name, void* p = 0); + + }; + +}} + +#endif + diff --git a/kexi/plugins/scripting/kexidb/kexidbparser.cpp b/kexi/plugins/scripting/kexidb/kexidbparser.cpp new file mode 100644 index 00000000..b022570d --- /dev/null +++ b/kexi/plugins/scripting/kexidb/kexidbparser.cpp @@ -0,0 +1,77 @@ +/*************************************************************************** + * kexidbparser.cpp + * This file is part of the KDE project + * copyright (C)2004-2005 by Sebastian Sauer (mail@dipe.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 "kexidbparser.h" +#include "kexidbschema.h" +#include "kexidbconnection.h" + +#include <api/variant.h> + +using namespace Kross::KexiDB; + +KexiDBParser::KexiDBParser(KexiDBConnection* connection, ::KexiDB::Parser* parser) + : Kross::Api::Class<KexiDBParser>("KexiDBParser") + , m_connection(connection) + , m_parser(parser) +{ + this->addFunction1< Kross::Api::Variant, Kross::Api::Variant >("parse", this, &KexiDBParser::parse); + this->addFunction0< void >("clear", this, &KexiDBParser::clear); + + this->addFunction0< Kross::Api::Variant >("operation", this, &KexiDBParser::operation); + + this->addFunction0< KexiDBTableSchema >("table", this, &KexiDBParser::table); + this->addFunction0< KexiDBQuerySchema >("query", this, &KexiDBParser::query); + this->addFunction0< KexiDBConnection >("connection", this, &KexiDBParser::connection); + this->addFunction0< Kross::Api::Variant >("statement", this, &KexiDBParser::statement); + + this->addFunction0< Kross::Api::Variant >("errorType", this, &KexiDBParser::errorType); + this->addFunction0< Kross::Api::Variant >("errorMsg", this, &KexiDBParser::errorMsg); + this->addFunction0< Kross::Api::Variant >("errorAt", this, &KexiDBParser::errorAt); +} + +KexiDBParser::~KexiDBParser() +{ +} + +const QString KexiDBParser::getClassName() const +{ + return "Kross::KexiDB::KexiDBParser"; +} + +bool KexiDBParser::parse(const QString& sql) { return m_parser->parse(sql); } +void KexiDBParser::clear() { m_parser->clear(); } +const QString KexiDBParser::operation() { return m_parser->operationString(); } + +KexiDBTableSchema* KexiDBParser::table() { + ::KexiDB::TableSchema* t = m_parser->table(); + return t ? new KexiDBTableSchema(t) : 0; +} + +KexiDBQuerySchema* KexiDBParser::query() { + ::KexiDB::QuerySchema* q = m_parser->query(); + return q ? new KexiDBQuerySchema(q) : 0; +} + +KexiDBConnection* KexiDBParser::connection() { return m_connection; } +const QString KexiDBParser::statement() { return m_parser->statement(); } + +const QString KexiDBParser::errorType() { return m_parser->error().type(); } +const QString KexiDBParser::errorMsg() { return m_parser->error().error(); } +int KexiDBParser::errorAt() { return m_parser->error().at(); } diff --git a/kexi/plugins/scripting/kexidb/kexidbparser.h b/kexi/plugins/scripting/kexidb/kexidbparser.h new file mode 100644 index 00000000..09ac22da --- /dev/null +++ b/kexi/plugins/scripting/kexidb/kexidbparser.h @@ -0,0 +1,95 @@ +/*************************************************************************** + * kexidbparser.h + * This file is part of the KDE project + * copyright (C)2004-2005 by Sebastian Sauer (mail@dipe.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 KROSS_KEXIDB_KEXIDBPARSER_H +#define KROSS_KEXIDB_KEXIDBPARSER_H + +#include <qstring.h> + +#include <api/object.h> +#include <api/list.h> +#include <api/class.h> + +#include <kexidb/drivermanager.h> +#include <kexidb/parser/parser.h> + +namespace Kross { namespace KexiDB { + + // Forward declaration. + class KexiDBConnection; + class KexiDBTableSchema; + class KexiDBQuerySchema; + + /** + * The KexiDBParser could be used to parse SQL-statements. + * + * Example (in Python) ; + * @code + * # First we need a parser object. + * parser = connection.parser() + * # Parse a SQL-statement. + * parser.parse("SELECT * from table1") + * # The operation could be e.g. SELECT or INSERT. + * if parser.operation() == 'Error': + * raise parser.errorMsg() + * # Print some feedback. + * print "Successfully parsed the SQL-statement %s" % parser.statement() + * @endcode + */ + class KexiDBParser : public Kross::Api::Class<KexiDBParser> + { + public: + KexiDBParser(KexiDBConnection* connection, ::KexiDB::Parser* parser); + virtual ~KexiDBParser(); + virtual const QString getClassName() const; + + private: + + /** Clears previous results and runs the parser on the SQL statement passed as an argument. */ + bool parse(const QString& sql); + /** Clears parsing results. */ + void clear(); + /** Returns the resulting operation. */ + const QString operation(); + + /** Returns the KexiDBTableSchema object on a CREATE TABLE operation. */ + KexiDBTableSchema* table(); + /** Returns the KexiDBQuerySchema object on a SELECT operation. */ + KexiDBQuerySchema* query(); + /** Returns the KexiDBConnection object pointing to the used database connection. */ + KexiDBConnection* connection(); + /** Returns the SQL query statement. */ + const QString statement(); + + /** Returns the type string of the last error. */ + const QString errorType(); + /** Returns the message of the last error. */ + const QString errorMsg(); + /** Returns the position where the last error occurred. */ + int errorAt(); + + private: + KexiDBConnection* m_connection; + ::KexiDB::Parser* m_parser; + }; + +}} + +#endif + diff --git a/kexi/plugins/scripting/kexidb/kexidbschema.cpp b/kexi/plugins/scripting/kexidb/kexidbschema.cpp new file mode 100644 index 00000000..e07917f3 --- /dev/null +++ b/kexi/plugins/scripting/kexidb/kexidbschema.cpp @@ -0,0 +1,197 @@ +/*************************************************************************** + * kexidbschema.cpp + * This file is part of the KDE project + * copyright (C)2004-2005 by Sebastian Sauer (mail@dipe.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 "kexidbschema.h" +#include "kexidbfieldlist.h" + +#include <qregexp.h> +#include <kdebug.h> + +#include <api/variant.h> + +using namespace Kross::KexiDB; + +/*************************************************************************** + *KexiDBSchema + */ + +template<class T> +KexiDBSchema<T>::KexiDBSchema(const QString& name, ::KexiDB::SchemaData* schema, ::KexiDB::FieldList* fieldlist) + : Kross::Api::Class<T>(name) + , m_schema(schema) + , m_fieldlist(fieldlist) +{ + this->template addFunction0<Kross::Api::Variant>("name", this, &KexiDBSchema<T>::name); + this->template addFunction1<void, Kross::Api::Variant>("setName", this, &KexiDBSchema<T>::setName); + + this->template addFunction0<Kross::Api::Variant>("caption", this, &KexiDBSchema<T>::caption); + this->template addFunction1<void, Kross::Api::Variant>("setCaption", this, &KexiDBSchema<T>::setCaption); + + this->template addFunction0<Kross::Api::Variant>("description", this, &KexiDBSchema<T>::description); + this->template addFunction1<void, Kross::Api::Variant>("setDescription", this, &KexiDBSchema<T>::setDescription); + + this->template addFunction0<KexiDBFieldList>("fieldlist", this, &KexiDBSchema<T>::fieldlist); +} + +template<class T> +KexiDBSchema<T>::~KexiDBSchema<T>() { +} + +template<class T> +const QString KexiDBSchema<T>::name() const { + return m_schema->name(); +} + +template<class T> +void KexiDBSchema<T>::setName(const QString& name) { + m_schema->setName(name); +} + +template<class T> +const QString KexiDBSchema<T>::caption() const { + return m_schema->caption(); +} + +template<class T> +void KexiDBSchema<T>::setCaption(const QString& caption) { + m_schema->setCaption(caption); +} + +template<class T> +const QString KexiDBSchema<T>::description() const { + return m_schema->description(); +} + +template<class T> +void KexiDBSchema<T>::setDescription(const QString& description) { + m_schema->setDescription(description); +} + +template<class T> +KexiDBFieldList* KexiDBSchema<T>::fieldlist() const { + return new KexiDBFieldList(m_fieldlist); +} + +/*************************************************************************** + * KexiDBTableSchema + */ + +KexiDBTableSchema::KexiDBTableSchema(::KexiDB::TableSchema* tableschema) + : KexiDBSchema<KexiDBTableSchema>("KexiDBTableSchema", tableschema, tableschema) +{ + this->addFunction0<KexiDBQuerySchema>("query", this, &KexiDBTableSchema::query); +} + +KexiDBTableSchema::~KexiDBTableSchema() { +} + +const QString KexiDBTableSchema::getClassName() const { + return "Kross::KexiDB::KexiDBTableSchema"; +} + +::KexiDB::TableSchema* KexiDBTableSchema::tableschema() { + return static_cast< ::KexiDB::TableSchema* >(m_schema); +} + +KexiDBQuerySchema* KexiDBTableSchema::query() { + return new KexiDBQuerySchema( tableschema()->query() ); +} + +/*************************************************************************** + * KexiDBQuerySchema + */ + +KexiDBQuerySchema::KexiDBQuerySchema(::KexiDB::QuerySchema* queryschema) + : KexiDBSchema<KexiDBQuerySchema>("KexiDBQuerySchema", queryschema, queryschema) +{ + this->addFunction0<Kross::Api::Variant>("statement", this, &KexiDBQuerySchema::statement); + this->addFunction1<void, Kross::Api::Variant>("setStatement", this, &KexiDBQuerySchema::setStatement); + this->addFunction1<Kross::Api::Variant, Kross::Api::Variant>("setWhereExpression", this, &KexiDBQuerySchema::setWhereExpression); +} + +KexiDBQuerySchema::~KexiDBQuerySchema() { +} + +const QString KexiDBQuerySchema::getClassName() const { + return "Kross::KexiDB::KexiDBQuerySchema"; +} + +::KexiDB::QuerySchema* KexiDBQuerySchema::queryschema() { + return static_cast< ::KexiDB::QuerySchema* >(m_schema); +} + +const QString KexiDBQuerySchema::statement() const { + return static_cast< ::KexiDB::QuerySchema* >(m_schema)->statement(); +} + +void KexiDBQuerySchema::setStatement(const QString& statement) { + static_cast< ::KexiDB::QuerySchema* >(m_schema)->setStatement(statement); +} + +bool KexiDBQuerySchema::setWhereExpression(const QString& whereexpression) { + ::KexiDB::BaseExpr* oldexpr = static_cast< ::KexiDB::QuerySchema* >(m_schema)->whereExpression(); + + ///@todo use ::KexiDB::Parser for such kind of parser-functionality. + QString s = whereexpression; + try { + QRegExp re("[\"',]{1,1}"); + while(true) { + s.remove(QRegExp("^[\\s,]+")); + int pos = s.find('='); + if(pos < 0) break; + QString key = s.left(pos).stripWhiteSpace(); + s = s.mid(pos + 1).stripWhiteSpace(); + + QString value; + int sp = s.find(re); + if(sp >= 0) { + if(re.cap(0) == ",") { + value = s.left(sp).stripWhiteSpace(); + s = s.mid(sp+1).stripWhiteSpace(); + } + else { + int ep = s.find(re.cap(0),sp+1); + value = s.mid(sp+1,ep-1); + s = s.mid(ep + 1); + } + } + else { + value = s; + s = QString::null; + } + + ::KexiDB::Field* field = static_cast< ::KexiDB::QuerySchema* >(m_schema)->field(key); + if(! field) + throw Kross::Api::Exception::Ptr( new Kross::Api::Exception(QString("Invalid WHERE-expression: Field \"%1\" does not exists in tableschema \"%2\".").arg(key).arg(m_schema->name())) ); + + QVariant v(value); + if(! v.cast(field->variantType())) + throw Kross::Api::Exception::Ptr( new Kross::Api::Exception(QString("Invalid WHERE-expression: The for Field \"%1\" defined value is of type \"%2\" rather then the expected type \"%3\"").arg(key).arg(v.typeName()).arg(field->variantType())) ); + + static_cast< ::KexiDB::QuerySchema* >(m_schema)->addToWhereExpression(field,v); + } + } + catch(Kross::Api::Exception::Ptr e) { + Kross::krosswarning("Exception in Kross::KexiDB::KexiDBQuerySchema::setWhereExpression: "); + static_cast< ::KexiDB::QuerySchema* >(m_schema)->setWhereExpression(oldexpr); // fallback + return false; + } + return true; +} diff --git a/kexi/plugins/scripting/kexidb/kexidbschema.h b/kexi/plugins/scripting/kexidb/kexidbschema.h new file mode 100644 index 00000000..61b6bc88 --- /dev/null +++ b/kexi/plugins/scripting/kexidb/kexidbschema.h @@ -0,0 +1,134 @@ +/*************************************************************************** + * kexidbschema.h + * This file is part of the KDE project + * copyright (C)2004-2005 by Sebastian Sauer (mail@dipe.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 KROSS_KEXIDB_KEXIDBSCHEMA_H +#define KROSS_KEXIDB_KEXIDBSCHEMA_H + +#include <qstring.h> + +#include <api/object.h> +#include <api/class.h> + +#include <kexidb/drivermanager.h> +#include <kexidb/schemadata.h> +#include <kexidb/tableschema.h> +#include <kexidb/queryschema.h> + +namespace Kross { namespace KexiDB { + + // Forward-declarations. + class KexiDBFieldList; + class KexiDBQuerySchema; + + /** + * The KexiDBSchema object provides common functionality for schemas + * like KexiDBTableSchema or KexiDBQuerySchema. + * + * Example (in Python) ; + * @code + * # Get the tableschema from a KexiDBConnection object. + * tableschema = connection.tableSchema("dept") + * # Print some information. + * print "table=%s description=%s" % (tableschema.name(), tableschema.description()) + * # Get the "SELECT * FROM dept;" queryschema for the table. + * queryschema = tableschema.query() + * # Walk through the fields/columns the queryschema has and print the fieldnames. + * for field in queryschema.fieldlist().fields(): + * print "fieldname=%s" % field.name() + * # Execute the query. The returned KexiDBCursor object could be used then to iterate through the result. + * cursor = connection.executeQuerySchema(queryschema) + * @endcode + */ + template<class T> + class KexiDBSchema : public Kross::Api::Class<T> + { + public: + KexiDBSchema(const QString& name, ::KexiDB::SchemaData* schema, ::KexiDB::FieldList* fieldlist); + virtual ~KexiDBSchema(); + + private: + + /** Returns the name of the schema. */ + const QString name() const; + /** Set the name of the schema. */ + void setName(const QString& name); + + /** Returns the caption of the schema. */ + const QString caption() const; + /** Set the caption of the schema. */ + void setCaption(const QString& caption); + + /** Returns a description of the schema. */ + const QString description() const; + /** Set a description of the schema. */ + void setDescription(const QString& description); + + /** Returns the KexiDBFieldList object this schema has. */ + KexiDBFieldList* fieldlist() const; + + protected: + ::KexiDB::SchemaData* m_schema; + ::KexiDB::FieldList* m_fieldlist; + }; + + /** + * The KexiDBTableSchema object implements a KexiDBSchema for tables. + */ + class KexiDBTableSchema : public KexiDBSchema<KexiDBTableSchema> + { + public: + KexiDBTableSchema(::KexiDB::TableSchema* tableschema); + virtual ~KexiDBTableSchema(); + virtual const QString getClassName() const; + ::KexiDB::TableSchema* tableschema(); + + private: + + /** Return the KexiDBQuerySchema object that represents a + "SELECT * FROM this_KexiDBTableSchema_object" SQL-statement. */ + KexiDBQuerySchema* query(); + + }; + + /** + * The KexiDBTableSchema object implements a KexiDBSchema for queries. + */ + class KexiDBQuerySchema : public KexiDBSchema<KexiDBQuerySchema> + { + public: + KexiDBQuerySchema(::KexiDB::QuerySchema* queryschema); + virtual ~KexiDBQuerySchema(); + virtual const QString getClassName() const; + ::KexiDB::QuerySchema* queryschema(); + + private: + + /** Returns the SQL-statement of this query schema. */ + const QString statement() const; + /** Set the SQL-statement of this query schema. */ + void setStatement(const QString& statement); + /** Set the where-expression. */ + bool setWhereExpression(const QString& whereexpression); + + }; + +}} + +#endif + diff --git a/kexi/plugins/scripting/kexidb/kexidbtransaction.cpp b/kexi/plugins/scripting/kexidb/kexidbtransaction.cpp new file mode 100644 index 00000000..d4cdff24 --- /dev/null +++ b/kexi/plugins/scripting/kexidb/kexidbtransaction.cpp @@ -0,0 +1,52 @@ +/*************************************************************************** + * kexidbtransaction.cpp + * This file is part of the KDE project + * copyright (C)2004-2005 by Sebastian Sauer (mail@dipe.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 "kexidbtransaction.h" +#include "kexidbconnection.h" +#include <api/variant.h> + +//#include <kdebug.h> + +using namespace Kross::KexiDB; + +KexiDBTransaction::KexiDBTransaction(::KexiDB::Transaction& transaction) + : Kross::Api::Class<KexiDBTransaction>("KexiDBTransaction") + , m_transaction(transaction) +{ + this->addFunction0< Kross::Api::Variant >("isActive", this, &KexiDBTransaction::isActive); + this->addFunction0< Kross::Api::Variant >("isNull", this, &KexiDBTransaction::isNull); +} + +KexiDBTransaction::~KexiDBTransaction() +{ +} + +const QString KexiDBTransaction::getClassName() const +{ + return "Kross::KexiDB::KexiDBTransaction"; +} + +::KexiDB::Transaction& KexiDBTransaction::transaction() +{ + return m_transaction; +} + +bool KexiDBTransaction::isActive() const { return m_transaction.active(); } +bool KexiDBTransaction::isNull() const { return m_transaction.isNull(); } diff --git a/kexi/plugins/scripting/kexidb/kexidbtransaction.h b/kexi/plugins/scripting/kexidb/kexidbtransaction.h new file mode 100644 index 00000000..6a6b5785 --- /dev/null +++ b/kexi/plugins/scripting/kexidb/kexidbtransaction.h @@ -0,0 +1,62 @@ +/*************************************************************************** + * kexidbtransaction.h + * This file is part of the KDE project + * copyright (C)2004-2005 by Sebastian Sauer (mail@dipe.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 KROSS_KEXIDB_KEXIDBTRANSACTION_H +#define KROSS_KEXIDB_KEXIDBTRANSACTION_H + +#include <qstring.h> + +#include <api/class.h> + +#include <kexidb/drivermanager.h> +#include <kexidb/transaction.h> + +namespace Kross { namespace KexiDB { + + // Forward declaration. + class KexiDBConnection; + + /** + * Transactions are used to ensure that integrity of a database is + * maintained. + */ + class KexiDBTransaction : public Kross::Api::Class<KexiDBTransaction> + { + public: + KexiDBTransaction(::KexiDB::Transaction& transaction); + virtual ~KexiDBTransaction(); + virtual const QString getClassName() const; + ::KexiDB::Transaction& transaction(); + + private: + + /** Return true if the transaction is active (ie. started). */ + bool isActive() const; + + /** Return true if the transaction is uninitialized (null). */ + bool isNull() const; + + private: + ::KexiDB::Transaction& m_transaction; + }; + +}} + +#endif + diff --git a/kexi/plugins/scripting/kexidb/readme.dox b/kexi/plugins/scripting/kexidb/readme.dox new file mode 100644 index 00000000..c4e33a5b --- /dev/null +++ b/kexi/plugins/scripting/kexidb/readme.dox @@ -0,0 +1,32 @@ +/** @mainpage KrossKexiDB + * + * The Kross KexiDB module provides a scripting bridge to the + * KexiDB library. KexiDB is the database abstraction layer used + * within Kexi to deal with all supported database-backends like + * SQLite, MySQL and Postqre. + * + * The @a KexiDBDriverManager is the manager module which provides + * the entry point to access the KexiDB functionality from + * scripting languages like Python and Ruby. + * + * @see http://www.kexi-project.org/scripting/ + * @see http://kross.dipe.org + * @see http://www.kexi-project.org/wiki/wikiview/index.php?Scripting + * + * @section Legal + * + * @li copyright (C) 2004-2006 by Sebastian Sauer (mail AT dipe DOT 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. + */ diff --git a/kexi/plugins/scripting/kexiscripting/Makefile.am b/kexi/plugins/scripting/kexiscripting/Makefile.am new file mode 100644 index 00000000..ed3e2264 --- /dev/null +++ b/kexi/plugins/scripting/kexiscripting/Makefile.am @@ -0,0 +1,37 @@ +include $(top_srcdir)/kexi/Makefile.global + +kde_module_LTLIBRARIES = kexihandler_script.la + +kexihandler_script_la_SOURCES = \ + kexiscriptpart.cpp kexiscripteditor.cpp kexiscriptdesignview.cpp + +kexihandler_script_la_LDFLAGS = \ + $(KDE_PLUGIN) -module -no-undefined -Wnounresolved $(all_libraries) $(VER_INFO) + +kexihandler_script_la_LIBADD = \ + $(top_builddir)/lib/kross/main/libkrossmain.la \ + $(top_builddir)/kexi/core/libkexicore.la \ + $(top_builddir)/kexi/widget/libkexiextendedwidgets.la \ + $(top_builddir)/lib/koproperty/libkoproperty.la + +INCLUDES = \ + $(KOFFICE_INCLUDES) \ + -I$(top_srcdir)/lib \ + -I$(top_srcdir)/kexi/core \ + -I$(top_srcdir)/kexi \ + -I$(top_srcdir)/kexi/widget \ + $(all_includes) + +servicesdir=$(kde_servicesdir)/kexi +services_DATA=kexiscripthandler.desktop + +rcdir = $(kde_datadir)/kexi +rc_DATA = kexiscriptpartui.rc kexiscriptpartinstui.rc + +METASOURCES = AUTO + +SUBDIRS = . + +include ../../Makefile.common + +noinst_HEADERS = kexiscriptpart.h kexiscripteditor.h kexiscriptdesignview.h diff --git a/kexi/plugins/scripting/kexiscripting/kexiscriptdesignview.cpp b/kexi/plugins/scripting/kexiscripting/kexiscriptdesignview.cpp new file mode 100644 index 00000000..ff2f93d0 --- /dev/null +++ b/kexi/plugins/scripting/kexiscripting/kexiscriptdesignview.cpp @@ -0,0 +1,337 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Lucijan Busch <lucijan@gmx.at> + Copyright (C) 2004-2005 Jaroslaw Staniek <js@iidea.pl> + Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr> + Copyright (C) 2005 Sebastian Sauer <mail@dipe.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 "kexiscriptdesignview.h" +#include "kexiscripteditor.h" + +#include <kross/main/manager.h> +#include <kross/main/scriptcontainer.h> +#include <kross/main/scriptaction.h> +#include <kross/api/interpreter.h> + +#include <qlayout.h> +#include <qsplitter.h> +#include <qtimer.h> +#include <qdatetime.h> +#include <qdom.h> +#include <qstylesheet.h> +#include <ktextbrowser.h> +#include <kdebug.h> + +#include <kexidialogbase.h> +#include <kexidb/connection.h> + +/// @internal +class KexiScriptDesignViewPrivate +{ + public: + + /** + * The \a Kross::Api::ScriptAction instance which provides + * us access to the scripting framework Kross. + */ + Kross::Api::ScriptAction* scriptaction; + + /// The \a KexiScriptEditor to edit the scripting code. + KexiScriptEditor* editor; + + /// The \a KoProperty::Set used in the propertyeditor. + KoProperty::Set* properties; + + /// Boolean flag to avoid infinite recursion. + bool updatesProperties; + + /// Used to display statusmessages. + KTextBrowser* statusbrowser; +}; + +KexiScriptDesignView::KexiScriptDesignView(KexiMainWindow *mainWin, QWidget *parent, Kross::Api::ScriptAction* scriptaction) + : KexiViewBase(mainWin, parent, "KexiScriptDesignView") + , d( new KexiScriptDesignViewPrivate() ) +{ + d->scriptaction = scriptaction; + d->updatesProperties = false; + + QSplitter* splitter = new QSplitter(this); + splitter->setOrientation(Vertical); + QHBoxLayout* layout = new QHBoxLayout(this); + layout->addWidget(splitter); + + d->editor = new KexiScriptEditor(mainWin, splitter, "ScriptEditor"); + splitter->setFocusProxy(d->editor); + addChildView(d->editor); + setViewWidget(d->editor); + + d->statusbrowser = new KTextBrowser(splitter, "ScriptStatusBrowser"); + d->statusbrowser->setReadOnly(true); + d->statusbrowser->setTextFormat(QTextBrowser::RichText); + //d->browser->setWordWrap(QTextEdit::WidgetWidth); + d->statusbrowser->installEventFilter(this); + splitter->setResizeMode(d->statusbrowser, QSplitter::KeepSize); + + plugSharedAction( "data_execute", this, SLOT(execute()) ); + if(KexiEditor::isAdvancedEditor()) // the configeditor is only in advanced mode avaiable. + plugSharedAction( "script_config_editor", d->editor, SLOT(slotConfigureEditor()) ); + + loadData(); + + d->properties = new KoProperty::Set(this, "KexiScripting"); + connect(d->properties, SIGNAL( propertyChanged(KoProperty::Set&, KoProperty::Property&) ), + this, SLOT( slotPropertyChanged(KoProperty::Set&, KoProperty::Property&) )); + + // To schedule the initialize fixes a crasher in Kate. + QTimer::singleShot(50, this, SLOT( initialize() )); +} + +KexiScriptDesignView::~KexiScriptDesignView() +{ + delete d->properties; + delete d; +} + +Kross::Api::ScriptAction* KexiScriptDesignView::scriptAction() const +{ + return d->scriptaction; +} + +void KexiScriptDesignView::initialize() +{ + updateProperties(); + d->editor->initialize( d->scriptaction ); +} + +void KexiScriptDesignView::updateProperties() +{ + if(d->updatesProperties) + return; + d->updatesProperties = true; + + Kross::Api::Manager* manager = Kross::Api::Manager::scriptManager(); + + QString interpretername = d->scriptaction->getInterpreterName(); + Kross::Api::InterpreterInfo* info = interpretername.isEmpty() ? 0 : manager->getInterpreterInfo(interpretername); + + { + // if interpreter isn't defined or invalid, try to fallback. + QStringList list; + list << "python" << "ruby"; + QStringList::ConstIterator it( list.constBegin() ), end( list.constEnd() ); + while( (! info) && (it != end) ) { + interpretername = (*it); + info = manager->getInterpreterInfo(interpretername); + if(info) + d->scriptaction->setInterpreterName(interpretername); + ++it; + } + } + + if(info) { + d->properties->clear(); + + QStringList interpreters = manager->getInterpreters(); + KoProperty::Property::ListData* proplist = new KoProperty::Property::ListData(interpreters, interpreters); + KoProperty::Property* prop = new KoProperty::Property( + "language", // name + proplist, // ListData + d->scriptaction->getInterpreterName(), // value + i18n("Interpreter"), // caption + i18n("The used scripting interpreter."), // description + KoProperty::List // type + ); + d->properties->addProperty(prop); + + Kross::Api::InterpreterInfo::Option::Map options = info->getOptions(); + Kross::Api::InterpreterInfo::Option::Map::ConstIterator it, end( options.constEnd() ); + for( it = options.constBegin(); it != end; ++it) { + Kross::Api::InterpreterInfo::Option* option = it.data(); + KoProperty::Property* prop = new KoProperty::Property( + it.key().latin1(), // name + d->scriptaction->getOption(it.key(), option->value), // value + option->name, // caption + option->comment, // description + KoProperty::Auto // type + ); + d->properties->addProperty(prop); + } + } + + //propertySetSwitched(); + propertySetReloaded(true); + d->updatesProperties = false; +} + +KoProperty::Set* KexiScriptDesignView::propertySet() +{ + return d->properties; +} + +void KexiScriptDesignView::slotPropertyChanged(KoProperty::Set& /*set*/, KoProperty::Property& property) +{ + if(property.isNull()) + return; + + if(property.name() == "language") { + QString language = property.value().toString(); + kdDebug() << QString("KexiScriptDesignView::slotPropertyChanged() language=%1").arg(language) << endl; + d->scriptaction->setInterpreterName( language ); + // We assume Kross and the HighlightingInterface are using same + // names for the support languages... + d->editor->setHighlightMode( language ); + updateProperties(); + } + else { + bool ok = d->scriptaction->setOption( property.name(), property.value() ); + if(! ok) { + kdWarning() << QString("KexiScriptDesignView::slotPropertyChanged() unknown property '%1'.").arg(property.name()) << endl; + return; + } + } + + setDirty(true); +} + +void KexiScriptDesignView::execute() +{ + d->statusbrowser->clear(); + QTime time; + time.start(); + d->statusbrowser->append( i18n("Execution of the script \"%1\" started.").arg(d->scriptaction->name()) ); + + d->scriptaction->activate(); + if( d->scriptaction->hadException() ) { + QString errormessage = d->scriptaction->getException()->getError(); + d->statusbrowser->append(QString("<b>%2</b><br>").arg(QStyleSheet::escape(errormessage)) ); + + QString tracedetails = d->scriptaction->getException()->getTrace(); + d->statusbrowser->append( QStyleSheet::escape(tracedetails) ); + + long lineno = d->scriptaction->getException()->getLineNo(); + if(lineno >= 0) + d->editor->setLineNo(lineno); + } + else { + d->statusbrowser->append( i18n("Successfully executed. Time elapsed: %1ms").arg(time.elapsed()) ); + } +} + +bool KexiScriptDesignView::loadData() +{ + QString data; + if(! loadDataBlock(data)) { + kexipluginsdbg << "KexiScriptDesignView::loadData(): no DataBlock" << endl; + return false; + } + + QString errMsg; + int errLine; + int errCol; + + QDomDocument domdoc; + bool parsed = domdoc.setContent(data, false, &errMsg, &errLine, &errCol); + + if(! parsed) { + kexipluginsdbg << "KexiScriptDesignView::loadData() XML parsing error line: " << errLine << " col: " << errCol << " message: " << errMsg << endl; + return false; + } + + QDomElement scriptelem = domdoc.namedItem("script").toElement(); + if(scriptelem.isNull()) { + kexipluginsdbg << "KexiScriptDesignView::loadData(): script domelement is null" << endl; + return false; + } + + QString interpretername = scriptelem.attribute("language"); + Kross::Api::Manager* manager = Kross::Api::Manager::scriptManager(); + Kross::Api::InterpreterInfo* info = interpretername.isEmpty() ? 0 : manager->getInterpreterInfo(interpretername); + if(info) { + d->scriptaction->setInterpreterName(interpretername); + + Kross::Api::InterpreterInfo::Option::Map options = info->getOptions(); + Kross::Api::InterpreterInfo::Option::Map::ConstIterator it, end = options.constEnd(); + for( it = options.constBegin(); it != end; ++it) { + QString value = scriptelem.attribute( it.data()->name ); + if(! value.isNull()) { + QVariant v(value); + if( v.cast( it.data()->value.type() ) ) // preserve the QVariant's type + d->scriptaction->setOption(it.data()->name, v); + } + } + } + + d->scriptaction->setCode( scriptelem.text() ); + + return true; +} + +KexiDB::SchemaData* KexiScriptDesignView::storeNewData(const KexiDB::SchemaData& sdata, bool &cancel) +{ + KexiDB::SchemaData *s = KexiViewBase::storeNewData(sdata, cancel); + kexipluginsdbg << "KexiScriptDesignView::storeNewData(): new id:" << s->id() << endl; + + if(!s || cancel) { + delete s; + return 0; + } + + if(! storeData()) { + kdWarning() << "KexiScriptDesignView::storeNewData Failed to store the data." << endl; + //failure: remove object's schema data to avoid garbage + KexiDB::Connection *conn = parentDialog()->mainWin()->project()->dbConnection(); + conn->removeObject( s->id() ); + delete s; + return 0; + } + + return s; +} + +tristate KexiScriptDesignView::storeData(bool /*dontAsk*/) +{ + kexipluginsdbg << "KexiScriptDesignView::storeData(): " << parentDialog()->partItem()->name() << " [" << parentDialog()->id() << "]" << endl; + + QDomDocument domdoc("script"); + QDomElement scriptelem = domdoc.createElement("script"); + domdoc.appendChild(scriptelem); + + QString language = d->scriptaction->getInterpreterName(); + scriptelem.setAttribute("language", language); + + Kross::Api::InterpreterInfo* info = Kross::Api::Manager::scriptManager()->getInterpreterInfo(language); + if(info) { + Kross::Api::InterpreterInfo::Option::Map defoptions = info->getOptions(); + QMap<QString, QVariant>& options = d->scriptaction->getOptions(); + QMap<QString, QVariant>::ConstIterator it, end( options.constEnd() ); + for( it = options.constBegin(); it != end; ++it) { + if( defoptions.contains(it.key()) ) { // only remember options which the InterpreterInfo knows about... + scriptelem.setAttribute(it.key(), it.data().toString()); + } + } + } + + QDomText scriptcode = domdoc.createTextNode(d->scriptaction->getCode()); + scriptelem.appendChild(scriptcode); + + return storeDataBlock( domdoc.toString() ); +} + +#include "kexiscriptdesignview.moc" + diff --git a/kexi/plugins/scripting/kexiscripting/kexiscriptdesignview.h b/kexi/plugins/scripting/kexiscripting/kexiscriptdesignview.h new file mode 100644 index 00000000..cee1ed76 --- /dev/null +++ b/kexi/plugins/scripting/kexiscripting/kexiscriptdesignview.h @@ -0,0 +1,124 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Lucijan Busch <lucijan@gmx.at> + Copyright (C) 2004-2005 Jaroslaw Staniek <js@iidea.pl> + Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr> + Copyright (C) 2005 Sebastian Sauer <mail@dipe.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 KEXISCRIPTDESIGNVIEW_H +#define KEXISCRIPTDESIGNVIEW_H + +#include <kexiviewbase.h> + +#include <koproperty/set.h> +#include <koproperty/property.h> + +// Forward declarations. +class KexiScriptContainer; +class KexiScriptEditor; +class KexiScriptDesignViewPrivate; + +namespace Kross { namespace Api { + class ScriptAction; +}} + +/** + * The KexiScriptDesignView class provides the \a KexiViewBase to + * manage script modules in the design-view. The design-view + * is used to be able to view and edit the scripting code via + * a \a KexiScriptEditor instance. + */ +class KexiScriptDesignView : public KexiViewBase +{ + Q_OBJECT + + public: + + /** + * Constructor. + */ + KexiScriptDesignView(KexiMainWindow *mainWin, QWidget *parent, Kross::Api::ScriptAction* scriptaction); + + /** + * Destructor. + */ + virtual ~KexiScriptDesignView(); + + /** + * \return the \a Kross::Api::ScriptAction this \a KexiScriptDesignView + * is responsible for. + */ + Kross::Api::ScriptAction* scriptAction() const; + + /** + * \return a property set for this view. + */ + virtual KoProperty::Set* propertySet(); + + /** + * Try to call \a storeData with new data we like to store. On + * success the matching \a KexiDB::SchemaData is returned. + * + * \param sdata The source \a KexiDB::SchemaData instance. + * \param cancel Cancel on failure and don't try to clean + * possible temporary created data up. + * \return The matching \a KexiDB::SchemaData instance or NULL + * if storing failed. + */ + virtual KexiDB::SchemaData* storeNewData(const KexiDB::SchemaData& sdata, bool &cancel); + + /** + * Try to store the modified data in the already opened and + * currently used \a KexiDB::SchemaData instance. + */ + virtual tristate storeData(bool dontAsk = false); + + private slots: + + /** + * Deferred initialization. + */ + void initialize(); + + /** + * Handle changes in the property editor. + */ + void slotPropertyChanged(KoProperty::Set& set, KoProperty::Property& property); + + /** + * Update the \a KoProperty::Property::Dict propertymap of the + * interpreter-dependent options. + */ + void updateProperties(); + + /** + * Execute the scripting code. + */ + void execute(); + + private: + KexiScriptDesignViewPrivate* d; + + /** + * Load the data from XML source and fill the internally + * used \a Kross::Api::ScriptContainer instance. + */ + bool loadData(); +}; + +#endif diff --git a/kexi/plugins/scripting/kexiscripting/kexiscripteditor.cpp b/kexi/plugins/scripting/kexiscripting/kexiscripteditor.cpp new file mode 100644 index 00000000..a638af36 --- /dev/null +++ b/kexi/plugins/scripting/kexiscripting/kexiscripteditor.cpp @@ -0,0 +1,104 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Lucijan Busch <lucijan@gmx.at> + Copyright (C) 2004-2005 Jaroslaw Staniek <js@iidea.pl> + Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr> + Copyright (C) 2005 Sebastian Sauer <mail@dipe.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 "kexiscripteditor.h" + +#include <kross/main/scriptaction.h> + +#include <kdebug.h> +//#include <kparts/factory.h> +//#include <klibloader.h> +//#include <kmdimainfrm.h> +//#include <kmainwindow.h> +#include <kpopupmenu.h> + +#include <kexidialogbase.h> + +/// \internal d-pointer class +class KexiScriptEditor::Private +{ + public: + Kross::Api::ScriptAction* scriptaction; + Private() : scriptaction(0) {} +}; + +KexiScriptEditor::KexiScriptEditor(KexiMainWindow *mainWin, QWidget *parent, const char *name) + : KexiEditor(mainWin, parent, name) + , d( new Private() ) +{ +} + +KexiScriptEditor::~KexiScriptEditor() +{ + delete d; +} + +bool KexiScriptEditor::isInitialized() const +{ + return d->scriptaction != 0; +} + +void KexiScriptEditor::initialize(Kross::Api::ScriptAction* scriptaction) +{ + d->scriptaction = scriptaction; + Q_ASSERT(d->scriptaction); + + disconnect(this, SIGNAL(textChanged()), this, SLOT(slotTextChanged())); + + QString code = d->scriptaction->getCode(); + if(code.isNull()) { + // If there is no code we just add some information. +///@todo remove after release + code = "# " + QStringList::split("\n", i18n( + "This note will appear for a user in the script's source code " + "as a comment. Keep every row not longer than 60 characters and use '\n.'", + + "This is Technology Preview (BETA) version of scripting\n" + "support in Kexi. The scripting API may change in details\n" + "in the next Kexi version.\n" + "For more information and documentation see\n%1" + ).arg("http://www.kexi-project.org/scripting/"), true).join("\n# ") + "\n"; + } + KexiEditor::setText(code); + // We assume Kross and the HighlightingInterface are using same + // names for the support languages... + setHighlightMode(d->scriptaction->getInterpreterName()); + + clearUndoRedo(); + KexiEditor::setDirty(false); + connect(this, SIGNAL(textChanged()), this, SLOT(slotTextChanged())); +} + +void KexiScriptEditor::slotTextChanged() +{ + KexiScriptEditor::setDirty(true); + if(d->scriptaction) + d->scriptaction->setCode( KexiEditor::text() ); +} + +void KexiScriptEditor::setLineNo(long lineno) +{ + setCursorPosition(lineno, 0); +} + +#include "kexiscripteditor.moc" + diff --git a/kexi/plugins/scripting/kexiscripting/kexiscripteditor.h b/kexi/plugins/scripting/kexiscripting/kexiscripteditor.h new file mode 100644 index 00000000..1ef02ff9 --- /dev/null +++ b/kexi/plugins/scripting/kexiscripting/kexiscripteditor.h @@ -0,0 +1,77 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Lucijan Busch <lucijan@gmx.at> + Copyright (C) 2004-2005 Jaroslaw Staniek <js@iidea.pl> + Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr> + Copyright (C) 2005 Sebastian Sauer <mail@dipe.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 KEXISCRIPTEDITOR_H +#define KEXISCRIPTEDITOR_H + +#include <kexieditor.h> + +namespace Kross { namespace Api { + class ScriptAction; +}} + +/** + * The KexiEditor class embeds text editor + * for editing scripting code. + */ +class KexiScriptEditor : public KexiEditor +{ + Q_OBJECT + + public: + + /** + * Constructor. + */ + KexiScriptEditor(KexiMainWindow *mainWin, QWidget *parent, const char *name = 0); + + /** + * Destructor. + */ + virtual ~KexiScriptEditor(); + + /** + * \returns true if this editor is already initialized (\a initialize was + * called) else false is returned. + */ + bool isInitialized() const; + + /** + * Initializes the editor. Call this if you like to start + * with a clear editor instance. Thinks like the language + * highlighter will be reset, undo/redo are cleared and + * setDirty(false) is set. + */ + void initialize(Kross::Api::ScriptAction* scriptaction); + + public slots: + void slotTextChanged(); + void setLineNo(long); + + private: + /// \internal d-pointer class. + class Private; + /// \internal d-pointer instance. + Private* const d; +}; + +#endif diff --git a/kexi/plugins/scripting/kexiscripting/kexiscripthandler.desktop b/kexi/plugins/scripting/kexiscripting/kexiscripthandler.desktop new file mode 100644 index 00000000..66e5f9fb --- /dev/null +++ b/kexi/plugins/scripting/kexiscripting/kexiscripthandler.desktop @@ -0,0 +1,105 @@ +[Desktop Entry] +Type=Service +ServiceTypes=Kexi/Handler + +GenericName=Scripts +GenericName[bg]=Скриптове +GenericName[br]=Urzhiaouegoù +GenericName[ca]=Seqüències +GenericName[cs]=Skripty +GenericName[cy]=Sgriptiau +GenericName[da]=Scripter +GenericName[de]=Skripte +GenericName[el]=Σενάρια +GenericName[eo]=Skriptoj +GenericName[es]=Guiones +GenericName[et]=Skriptid +GenericName[eu]=Script-ak +GenericName[fa]=دستنوشتهها +GenericName[fi]=Skriptit +GenericName[fy]=Skripts +GenericName[ga]=Scripteanna +GenericName[gl]=Programas +GenericName[he]=תסריטים +GenericName[hr]=Skripte +GenericName[hu]=Szkriptek +GenericName[is]=Skriftur +GenericName[it]=Script +GenericName[ja]=スクリプト +GenericName[km]=ស្គ្រីប +GenericName[lt]=Scenarijai +GenericName[lv]=Skripti +GenericName[ms]=Skrip +GenericName[nb]=Skript +GenericName[nds]=Skripten +GenericName[ne]=स्क्रिप्टहरू +GenericName[nn]=Skript +GenericName[pl]=Skrypty +GenericName[pt]=Programas +GenericName[ru]=Сценарии +GenericName[se]=Skriptat +GenericName[sk]=Skripty +GenericName[sl]=Skripti +GenericName[sr]=Скрипте +GenericName[sr@Latn]=Skripte +GenericName[sv]=Skript +GenericName[uk]=Скрипти +GenericName[uz]=Skriptlar +GenericName[uz@cyrillic]=Скриптлар +GenericName[zh_CN]=脚本 +GenericName[zh_TW]=命令稿 +Name=Scripts +Name[bg]=Скриптове +Name[br]=Urzhiaouegoù +Name[ca]=Seqüències +Name[cs]=Skripty +Name[cy]=Sgriptiau +Name[da]=Scripter +Name[de]=Skripte +Name[el]=Σενάρια +Name[eo]=Skriptoj +Name[es]=Guiones +Name[et]=Skriptid +Name[eu]=Script-ak +Name[fa]=دستنوشتهها +Name[fi]=Skriptit +Name[fy]=Skripts +Name[ga]=Scripteanna +Name[gl]=Programas +Name[he]=תסריטים +Name[hr]=Skripte +Name[hu]=Szkriptek +Name[is]=Skriftur +Name[it]=Script +Name[ja]=スクリプト +Name[km]=ស្គ្រីប +Name[lt]=Scenarijai +Name[lv]=Skripti +Name[ms]=Skrip +Name[nb]=Skript +Name[nds]=Skripten +Name[ne]=स्क्रिप्टहरू +Name[nn]=Skript +Name[pl]=Skrypty +Name[pt]=Programas +Name[ru]=Сценарии +Name[se]=Skriptat +Name[sk]=Skripty +Name[sl]=Skripti +Name[sr]=Скрипте +Name[sr@Latn]=Skripte +Name[sv]=Skript +Name[uk]=Скрипти +Name[uz]=Skriptlar +Name[uz@cyrillic]=Скриптлар +Name[zh_CN]=脚本 +Name[zh_TW]=命令稿 +X-KDE-Library=kexihandler_script +X-KDE-ParentApp=kexi +X-Kexi-PartVersion=2 +X-Kexi-TypeName=script +X-Kexi-TypeMime=kexi/script +X-Kexi-ItemIcon=script +X-Kexi-SupportsExecution=true +X-Kexi-SupportsDataExport=false +X-Kexi-SupportsPrinting=false diff --git a/kexi/plugins/scripting/kexiscripting/kexiscriptpart.cpp b/kexi/plugins/scripting/kexiscripting/kexiscriptpart.cpp new file mode 100644 index 00000000..d650e958 --- /dev/null +++ b/kexi/plugins/scripting/kexiscripting/kexiscriptpart.cpp @@ -0,0 +1,201 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Lucijan Busch <lucijan@kde.org> + Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr> + Copyright (C) 2005 Sebastian Sauer <mail@dipe.org> + + 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 "kexiscriptpart.h" +#include "kexiscriptdesignview.h" + +#include "kexiviewbase.h" +#include "keximainwindow.h" +#include "kexiproject.h" + +#include <kross/main/manager.h> +#include <kross/main/scriptaction.h> +#include <kross/main/scriptguiclient.h> + +#include <kgenericfactory.h> +#include <kexipartitem.h> +#include <kxmlguiclient.h> +#include <kexidialogbase.h> +#include <kconfig.h> +#include <kdebug.h> + +/// \internal +class KexiScriptPart::Private +{ + public: + Kross::Api::ScriptGUIClient* scriptguiclient; +}; + +KexiScriptPart::KexiScriptPart(QObject *parent, const char *name, const QStringList &l) + : KexiPart::Part(parent, name, l) + , d( new Private() ) +{ + d->scriptguiclient = 0; + + // REGISTERED ID: + m_registeredPartID = (int)KexiPart::ScriptObjectType; + + 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.", + "script"); + m_names["instanceCaption"] = i18n("Script"); + m_supportedViewModes = Kexi::DesignViewMode; +} + +KexiScriptPart::~KexiScriptPart() +{ + delete d->scriptguiclient; + delete d; +} + +bool KexiScriptPart::execute(KexiPart::Item* item, QObject* sender) +{ + Q_UNUSED(sender); + + if(! item) { + kdWarning() << "KexiScriptPart::execute: Invalid item." << endl; + return false; + } + + KexiDialogBase* dialog = new KexiDialogBase(m_mainWin); + dialog->setId( item->identifier() ); + KexiScriptDesignView* view = dynamic_cast<KexiScriptDesignView*>( createView(dialog, dialog, *item, Kexi::DesignViewMode) ); + if(! view) { + kdWarning() << "KexiScriptPart::execute: Failed to create a view." << endl; + return false; + } + + Kross::Api::ScriptAction* scriptaction = view->scriptAction(); + if(scriptaction) { + + const QString dontAskAgainName = "askExecuteScript"; + KConfig* config = KGlobal::config(); + QString dontask = config->readEntry(dontAskAgainName).lower(); + + bool exec = (dontask == "yes"); + if( !exec && dontask != "no" ) { + exec = KMessageBox::warningContinueCancel(0, + i18n("Do you want to execute the script \"%1\"?\n\nScripts obtained from unknown sources can contain dangerous code.").arg(scriptaction->text()), + i18n("Execute Script?"), KGuiItem(i18n("Execute"), "exec"), + dontAskAgainName, KMessageBox::Notify | KMessageBox::Dangerous + ) == KMessageBox::Continue; + } + + if(exec) { + //QTimer::singleShot(10, scriptaction, SLOT(activate())); + d->scriptguiclient->executeScriptAction( scriptaction ); + } + } + + view->deleteLater(); // not needed any longer. + return true; +} + +void KexiScriptPart::initPartActions() +{ + if(m_mainWin) { + // At this stage the KexiPart::Part::m_mainWin should be defined, so + // that we are able to use it's KXMLGUIClient. + + // Initialize the ScriptGUIClient. + d->scriptguiclient = new Kross::Api::ScriptGUIClient( m_mainWin ); + + // Publish the KexiMainWindow singelton instance. At least the KexiApp + // scripting-plugin depends on this instance and loading the plugin will + // fail if it's not avaiable. + if(! Kross::Api::Manager::scriptManager()->hasChild("KexiMainWindow")) { + Kross::Api::Manager::scriptManager()->addQObject(m_mainWin, "KexiMainWindow"); + + // Add the KAction's provided by the ScriptGUIClient to the + // KexiMainWindow. + //FIXME: fix+use createSharedPartAction() whyever it doesn't work as expected right now... + QPopupMenu* popup = m_mainWin->findPopupMenu("tools"); + if(popup) { + KAction* execscriptaction = d->scriptguiclient->action("executescriptfile"); + if(execscriptaction) + execscriptaction->plug( popup ); + KAction* configscriptaction = d->scriptguiclient->action("configurescripts"); + if(configscriptaction) + configscriptaction->plug( popup ); + KAction* scriptmenuaction = d->scriptguiclient->action("installedscripts"); + if(scriptmenuaction) + scriptmenuaction->plug( popup ); + /* + KAction* execscriptmenuaction = d->scriptguiclient->action("executedscripts"); + if(execscriptmenuaction) + execscriptmenuaction->plug( popup ); + KAction* loadedscriptmenuaction = d->scriptguiclient->action("loadedscripts"); + if(loadedscriptmenuaction) + loadedscriptmenuaction->plug( popup ); + */ + } + } + } +} + +void KexiScriptPart::initInstanceActions() +{ + //createSharedAction(Kexi::DesignViewMode, i18n("Execute Script"), "player_play", 0, "data_execute"); + createSharedAction(Kexi::DesignViewMode, i18n("Configure Editor..."), "configure", 0, "script_config_editor"); +} + +KexiViewBase* KexiScriptPart::createView(QWidget *parent, KexiDialogBase* dialog, KexiPart::Item& item, int viewMode, QMap<QString,QString>*) +{ + QString partname = item.name(); + if( ! partname.isNull() ) { + KexiMainWindow *win = dialog->mainWin(); + if(!win || !win->project() || !win->project()->dbConnection()) + return 0; + + Kross::Api::ScriptActionCollection* collection = d->scriptguiclient->getActionCollection("projectscripts"); + if(! collection) { + collection = new Kross::Api::ScriptActionCollection( i18n("Scripts"), d->scriptguiclient->actionCollection(), "projectscripts" ); + d->scriptguiclient->addActionCollection("projectscripts", collection); + } + + const char* name = partname.latin1(); + Kross::Api::ScriptAction::Ptr scriptaction = collection->action(name); + if(! scriptaction) { + scriptaction = new Kross::Api::ScriptAction(partname); + collection->attach(scriptaction); //TODO remove again on unload! + } + + if(viewMode == Kexi::DesignViewMode) { + return new KexiScriptDesignView(win, parent, scriptaction); + } + } + return 0; +} + +QString KexiScriptPart::i18nMessage(const QCString& englishMessage) const +{ + if (englishMessage=="Design of object \"%1\" has been modified.") + return i18n("Design of script \"%1\" has been modified."); + if (englishMessage=="Object \"%1\" already exists.") + return i18n("Script \"%1\" already exists."); + return englishMessage; +} + +K_EXPORT_COMPONENT_FACTORY( kexihandler_script, KGenericFactory<KexiScriptPart>("kexihandler_script") ) + +#include "kexiscriptpart.moc" diff --git a/kexi/plugins/scripting/kexiscripting/kexiscriptpart.h b/kexi/plugins/scripting/kexiscripting/kexiscriptpart.h new file mode 100644 index 00000000..ddba0d72 --- /dev/null +++ b/kexi/plugins/scripting/kexiscripting/kexiscriptpart.h @@ -0,0 +1,100 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Lucijan Busch <lucijan@kde.org> + Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr> + Copyright (C) 2005 Sebastian Sauer <mail@dipe.org> + + 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 KEXISCRIPTPART_H +#define KEXISCRIPTPART_H + +#include <qdom.h> +#include <qcstring.h> + +#include <kexi.h> +#include <kexipart.h> +#include <kexidialogbase.h> + +/** + * Kexi Scripting Plugin. + */ +class KexiScriptPart : public KexiPart::Part +{ + Q_OBJECT + + public: + + /** + * Constructor. + * + * \param parent The parent QObject this part is child of. + * \param name The name this part has. + * \param args Optional list of arguments passed to this part. + */ + KexiScriptPart(QObject *parent, const char *name, const QStringList& args); + + /** + * Destructor. + */ + virtual ~KexiScriptPart(); + + /** + * Implementation of the \a KexiPart::Part::execute method used to + * execute the passed \p item instance. + */ + virtual bool execute(KexiPart::Item* item, QObject* sender = 0); + + /** + * \return the i18n message for the passed \p englishMessage string. + */ + virtual QString i18nMessage(const QCString& englishMessage) const; + + protected: + + /** + * Create a new view. + * + * \param parent The parent QWidget the new view is displayed in. + * \param dialog The \a KexiDialogBase the view is child of. + * \param item The \a KexiPart::Item this view is for. + * \param viewMode The viewmode we like to have a view for. + */ + virtual KexiViewBase* createView(QWidget *parent, + KexiDialogBase* dialog, + KexiPart::Item& item, + int viewMode = Kexi::DesignViewMode, + QMap<QString,QString>* staticObjectArgs = 0); + + /** + * Initialize the part's actions. + */ + virtual void initPartActions(); + + /** + * Initialize the instance actions. + */ + virtual void initInstanceActions(); + + private: + /// \internal d-pointer class. + class Private; + /// \internal d-pointer instance. + Private* const d; +}; + +#endif + diff --git a/kexi/plugins/scripting/kexiscripting/kexiscriptpartinstui.rc b/kexi/plugins/scripting/kexiscripting/kexiscriptpartinstui.rc new file mode 100644 index 00000000..16124c34 --- /dev/null +++ b/kexi/plugins/scripting/kexiscripting/kexiscriptpartinstui.rc @@ -0,0 +1,10 @@ +<!DOCTYPE kpartgui> +<kpartgui name="kexiscriptpartinst" version="14"> + + <MenuBar> + <Menu name="settings" noMerge="1"> + <Action name="script_config_editor"/> + </Menu> + </MenuBar> + +</kpartgui> diff --git a/kexi/plugins/scripting/kexiscripting/kexiscriptpartui.rc b/kexi/plugins/scripting/kexiscripting/kexiscriptpartui.rc new file mode 100644 index 00000000..171d643f --- /dev/null +++ b/kexi/plugins/scripting/kexiscripting/kexiscriptpartui.rc @@ -0,0 +1,10 @@ +<!DOCTYPE kpartgui> +<kpartgui name="kexiscriptpart" version="10"> + + <MenuBar> + <Menu name="settings" noMerge="1"> + <Action name="script_config_editor"/> + </Menu> + </MenuBar> + +</kpartgui> diff --git a/kexi/plugins/scripting/scripts/Makefile.am b/kexi/plugins/scripting/scripts/Makefile.am new file mode 100644 index 00000000..b63eedee --- /dev/null +++ b/kexi/plugins/scripting/scripts/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = exportxhtml importxhtml projectdocumentor copycenter python diff --git a/kexi/plugins/scripting/scripts/copycenter/CopyCenter.py b/kexi/plugins/scripting/scripts/copycenter/CopyCenter.py new file mode 100644 index 00000000..3718512f --- /dev/null +++ b/kexi/plugins/scripting/scripts/copycenter/CopyCenter.py @@ -0,0 +1,644 @@ +""" +Copy Center + +Description: +Python script to copy data between different datastores. + +Author: +Sebastian Sauer <mail@dipe.org> + +Copyright: +Dual-licensed under LGPL v2+higher and the BSD license. +""" + +class CopyCenter: + + class Plugin: + def __init__(self, plugin): + self.plugin = plugin + self.name = plugin.name + self.source = self.load("Source") + self.destination = self.load("Destination") + + def load(self, plugintype): + instance = None + try: + if hasattr(self.plugin, plugintype): + return getattr(self.plugin, plugintype)(self.plugin) + except: + import traceback + print "".join( traceback.format_exception(sys.exc_info()[0],sys.exc_info()[1],sys.exc_info()[2]) ) + return None + + def __init__(self, scriptpath): + self.scriptpath = scriptpath + self.homepath = self.getHomePath() + self.plugins = {} + + import os + import sys + if not os.path.exists(scriptpath): + print "The Path %s does not exist" % scriptpath + else: + import re + regexp = re.compile('^CopyCenterPlugin(.*)\\.py$') + for f in os.listdir(scriptpath): + file = os.path.join(scriptpath, f) + if not os.path.isfile(file): continue + m = regexp.match(f) + if not m: continue + print "Plugin name=%s file=%s" % (m.group(1),file) + mylocals = {} + try: + execfile(file, globals(), mylocals) + if mylocals.has_key("CopyCenterPlugin"): + plugin = mylocals.get("CopyCenterPlugin")(self) + self.plugins[plugin.name] = self.Plugin(plugin) + except: + print "Failed to import file=%s" % file + import traceback + print "".join( traceback.format_exception(sys.exc_info()[0],sys.exc_info()[1],sys.exc_info()[2]) ) + + def getHomePath(self): + """ Return the homedirectory. """ + import os + try: + home = os.getenv("HOME") + if not home: + import pwd + user = os.getenv("USER") or os.getenv("LOGNAME") + if not user: + pwent = pwd.getpwuid(os.getuid()) + else: + pwent = pwd.getpwnam(user) + home = pwent[6] + return home + except (KeyError, ImportError): + return os.curdir + +class Copierer: + def __init__(self): pass + def appendProgressMessage(self,messagetext): pass + def writeSuccess(self,record,rowcount): pass + def writeFailed(self,record): pass + +def runGuiApp(copycenter, name): + import qt + import sys + + #-------------------------------------------------------------------- + + class ListViewDialog(qt.QDialog): + def __init__(self, parent, caption): + qt.QDialog.__init__(self, parent, "ProgressDialog", 1) + self.parent = parent + self.setCaption(caption) + layout = qt.QVBoxLayout(self) + box = qt.QVBox(self) + box.setMargin(2) + layout.addWidget(box) + self.listview = qt.QListView(box) + self.listview.setAllColumnsShowFocus(True) + self.listview.header().setStretchEnabled(True,0) + btnbox = qt.QHBox(box) + btnbox.setMargin(6) + btnbox.setSpacing(6) + self.okbtn = qt.QPushButton(btnbox) + self.okbtn.setText("Ok") + #qt.QObject.connect(okbtn, qt.SIGNAL("clicked()"), self.okClicked) + self.cancelbtn = qt.QPushButton(btnbox) + self.cancelbtn.setText("Cancel") + qt.QObject.connect(self.cancelbtn, qt.SIGNAL("clicked()"), self.close) + box.setMinimumSize(qt.QSize(460,380)) + def addItem(self,valuelist,afteritem = None): + if afteritem == None: + item = qt.QListViewItem(self.listview) + else: + item = qt.QListViewItem(self.listview,afteritem) + i = 0 + for value in valuelist: + item.setText(i,value) + i += 1 + return item + + #-------------------------------------------------------------------- + + class CopyJobWidget(qt.QVBox): + def __init__(self,dialog,parent): + self.dialog = dialog + qt.QVBox.__init__(self,parent) + self.setSpacing(6) + typebox = qt.QHBox(self) + typebox.setSpacing(6) + label = qt.QLabel("Job File:",typebox) + self.jobfilecombobox = qt.QComboBox(typebox) + typebox.setStretchFactor(self.jobfilecombobox,1) + self.jobfilecombobox.setEditable(True) + self.jobfilecombobox.insertItem("") + label.setBuddy(self.jobfilecombobox) + qt.QObject.connect(self.jobfilecombobox, qt.SIGNAL("textChanged(const QString&)"), self.jobfilecomboboxChanged) + + import os + import re + for f in os.listdir(self.dialog.copycenter.homepath): + file = os.path.join(self.dialog.copycenter.homepath,f) + if os.path.isfile(file) and re.search(".+\\.copycenterjob.xml$",f): + self.jobfilecombobox.insertItem(file) + + loadbtn = qt.QPushButton(typebox) + loadbtn.setText("Open...") + qt.QObject.connect(loadbtn, qt.SIGNAL("clicked()"), self.openClicked) + savebtn = qt.QPushButton(typebox) + savebtn.setText("Save...") + qt.QObject.connect(savebtn, qt.SIGNAL("clicked()"), self.saveClicked) + + self.listview = qt.QListView(self) + self.listview.setAllColumnsShowFocus(True) + self.listview.setSorting(-1) + self.listview.setDefaultRenameAction(qt.QListView.Reject) + self.listview.header().setClickEnabled(False) + self.listview.addColumn("Name") + self.listview.addColumn("Value") + qt.QObject.connect(self.listview, qt.SIGNAL("doubleClicked(QListViewItem*, const QPoint&, int)"), self.doubleClicked) + #qt.QObject.connect(self.listview, qt.SIGNAL("itemRenamed(QListViewItem*, int, const QString&)"), self.itemRenamed) + + def doubleClicked(self, **args): + print "CopyJobWidget.doubleClicked" + item = self.listview.selectedItem() + if item and item.parent(): item.startRename(1) + + def readOptions(self,domnode,plugininst): + print "CopyJobWidget.readOptions plugintype=\"%s\"" % plugininst.plugintype + for node in domnode.childNodes: + if node.nodeType == node.ELEMENT_NODE: + v = node.getAttribute("value") + plugininst.options[node.nodeName] = v + print "Option \"%s\" has value \"%s\" now." % (node.nodeName, v) + + def jobfilecomboboxChanged(self, **args): + print "CopyJobWidget.jobfilecomboboxChanged" + import os + import xml.dom.minidom + filename = str(self.jobfilecombobox.currentText()) + if not os.path.isfile(filename): return + domdoc = xml.dom.minidom.parse(filename) + try: + elements = domdoc.getElementsByTagName("CopyCenterJob")[0] + sourcenode = elements.getElementsByTagName("Source")[0] + destinationnode = elements.getElementsByTagName("Destination")[0] + except: + raise "The XML-file \"%s\" does not contain a valid copy-job." % filename + + sourcepluginname = str(sourcenode.getAttribute('plugin')) + if not self.dialog.sourcedata.combobox.listBox().findItem(sourcepluginname,qt.Qt.ExactMatch): + raise "There exists no plugin with the name \"%s\"." % sourcepluginname + self.dialog.sourcedata.combobox.setCurrentText(sourcepluginname) + + destinationpluginname = str(destinationnode.getAttribute('plugin')) + if not self.dialog.destinationdata.combobox.listBox().findItem(destinationpluginname,qt.Qt.ExactMatch): + raise "There exists no plugin with the name \"%s\"." % destinationpluginname + self.dialog.destinationdata.combobox.setCurrentText(destinationpluginname) + + self.readOptions(sourcenode,self.dialog.getSourcePluginImpl()) + self.readOptions(destinationnode,self.dialog.getDestinationPluginImpl()) + self.maybeUpdate() + + def openClicked(self): + text = str(self.jobfilecombobox.currentText()) + if text == "": text = self.dialog.copycenter.homepath + filename = str(qt.QFileDialog.getOpenFileName(text,"*.copycenterjob.xml;;*",self.dialog)) + if filename != "": self.jobfilecombobox.setCurrentText(filename) + + def escape(self,s): + return s.replace("&", "&").replace("'", "'").replace("<", "<").replace(">", ">").replace('"', """) + + def writeOptions(self,writer,pluginname,plugininst): + print "CopyJobWidget.writeOptions" + writer.write("<%s plugin=\"%s\">\n" % (plugininst.plugintype, pluginname)) + for optionname in plugininst.options: + value = self.escape( unicode(plugininst.options[optionname]).encode("utf-8") ) + writer.write("\t<%s value=\"%s\" />\n" % (optionname,value)) + writer.write("</%s>\n" % plugininst.plugintype) + + def saveClicked(self): + text = str(self.jobfilecombobox.currentText()) + if text == "": + import os + text = os.path.join(self.dialog.copycenter.homepath,"default.copycenterjob.xml") + filename = str(qt.QFileDialog.getSaveFileName(text,"*.copycenterjob.xml;;*",self.dialog)) + if str(filename) == "": return + f = open(filename, "w") + f.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n") + f.write("<CopyCenterJob>\n") + sourcepluginname = self.dialog.sourcedata.combobox.currentText() + self.writeOptions(f, sourcepluginname, self.dialog.getSourcePluginImpl()) + destinationpluginname = self.dialog.destinationdata.combobox.currentText() + self.writeOptions(f, destinationpluginname, self.dialog.getDestinationPluginImpl()) + f.write("</CopyCenterJob>\n") + f.close() + print "File \%s\" successfully written." % filename + + def addItem(self, pluginimpl, afteritem = None, parentitem = None): + #print "CopyJobWidget.addItem" + class ListViewItem(qt.QListViewItem): + def __init__(self, pluginimpl, listview, parentitem = None, afteritem = None): + self.pluginimpl = pluginimpl + if parentitem == None: + qt.QListViewItem.__init__(self,listview) + self.setOpen(True) + else: + if afteritem == None: + qt.QListViewItem.__init__(self,parentitem) + else: + qt.QListViewItem.__init__(self,parentitem,afteritem) + self.setRenameEnabled(1,True) + def startRename(self, columnindex): + qt.QListViewItem.startRename(self,columnindex) + #lineedit = self.listView().viewport().child("qt_renamebox") + #if lineedit: + # regexp = qt.QRegExp("^[_A-Z]+[_A-Z0-9]*$", False) + # v = qt.QRegExpValidator(regexp, self.listView()); + # lineedit.setValidator(v) + def okRename(self, columnindex): + if columnindex == 1: + n = str(self.text(0)) + if not self.pluginimpl.options.has_key(n): + raise "No such option \"%s\"" % n + qt.QListViewItem.okRename(self,columnindex) + v = str(qt.QListViewItem.text(self,1)) + print "Option \"%s\" has value \"%s\" now." % (n,v) + self.pluginimpl.options[n] = v + + def text(self, columnindex): + if columnindex == 1: + if qt.QListViewItem.text(self,0).contains("password"): + return "*" * len(str(qt.QListViewItem.text(self,1))) + return qt.QListViewItem.text(self,columnindex) + return ListViewItem(pluginimpl, self.listview, parentitem, afteritem) + + def updateItem(self,pluginname,pluginimpl): + #print "CopyJobWidget.updateItem" + if pluginimpl == None: return + #plugin = self.dialog.plugins[pluginname] + item = self.addItem(pluginimpl) + item.setText(0,"%s: %s" % (pluginimpl.plugintype, pluginname)) + afteritem = None + for i in pluginimpl.options: + afteritem = self.addItem(pluginimpl, afteritem, item) + afteritem.setText(0,str(i)) + afteritem.setText(1,str(pluginimpl.options[i])) + print "CopyJobWidget.updateItem Added item with name \"%s\" and value \"%s\"" % (str(i),str(pluginimpl.options[i])) + pass + + def maybeUpdate(self): + print "CopyJobWidget.maybeUpdate" + self.listview.clear() + try: + self.updateItem(self.dialog.getDestinationPluginName(), self.dialog.getDestinationPluginImpl()) + self.updateItem(self.dialog.getSourcePluginName(), self.dialog.getSourcePluginImpl()) + except: + import traceback + print "".join( traceback.format_exception(sys.exc_info()[0],sys.exc_info()[1],sys.exc_info()[2]) ) + self.listview.clear() + + #-------------------------------------------------------------------- + + class ProgressDialog(qt.QDialog): + def __init__(self, dialog): + self.dialog = dialog + self.starttime = None + qt.QDialog.__init__(self, dialog, "ProgressDialog", 1) + self.setCaption("Copying...") + layout = qt.QVBoxLayout(self) + box = qt.QVBox(self) + box.setSpacing(6) + box.setMargin(6) + layout.addWidget(box) + self.textbrowser = qt.QTextBrowser(box) + self.textbrowser.setWordWrap(qt.QTextEdit.WidgetWidth) + self.textbrowser.setTextFormat(qt.Qt.RichText) + statusbox = qt.QFrame(box) + layout = qt.QGridLayout(statusbox,4,2,0,2) + layout.addWidget(qt.QLabel("Number of records done:",statusbox),0,0) + self.donecounter = 0 + self.donelabel = qt.QLabel("-",statusbox) + layout.addWidget(self.donelabel,0,1) + layout.addWidget(qt.QLabel("Successfully copied records:",statusbox),1,0) + self.successcounter = 0 + self.successlabel = qt.QLabel("-",statusbox) + layout.addWidget(self.successlabel,1,1) + layout.addWidget(qt.QLabel("Failed to copy records:",statusbox),2,0) + self.failedcounter = 0 + self.failedlabel = qt.QLabel("-",statusbox) + layout.addWidget(self.failedlabel,2,1) + layout.addWidget(qt.QLabel("Elapsed time in seconds:",statusbox),3,0) + self.elapsedlabel = qt.QLabel("-",statusbox) + layout.addWidget(self.elapsedlabel,3,1) + btnbox = qt.QHBox(box) + btnbox.setSpacing(6) + self.donebtn = qt.QPushButton(btnbox) + self.donebtn.setText("Done") + self.donebtn.setEnabled(False) + qt.QObject.connect(self.donebtn,qt.SIGNAL("clicked()"),self.close) + self.cancelbtn = qt.QPushButton(btnbox) + self.cancelbtn.setText("Cancel") + qt.QObject.connect(self.cancelbtn,qt.SIGNAL("clicked()"),self.close) + box.setMinimumSize( qt.QSize(500,380) ) + + def updateStates(self): + if self.starttime != None: + self.donelabel.setText(str(self.donecounter)) + self.failedlabel.setText(str(self.failedcounter)) + self.successlabel.setText(str(self.successcounter)) + self.elapsedlabel.setText( str(self.starttime.elapsed() / 1000) ) + self.donelabel.update() + self.failedlabel.update() + self.successlabel.update() + self.elapsedlabel.update() + + def writeSuccess(self, record, rowcount): + self.donecounter += rowcount + self.successcounter += rowcount + qt.qApp.processEvents() + def writeFailed(self, record): + self.donecounter += 1 + self.failedcounter += 1 + qt.qApp.processEvents() + + def startCopy(self): + try: + global Copierer + copierer = Copierer() + copierer.appendProgressMessage = self.textbrowser.append + copierer.writeSuccess = self.writeSuccess + copierer.writeFailed = self.writeFailed + + self.starttime = qt.QTime() + self.updatetimer = qt.QTimer(self) + qt.QObject.connect(self.updatetimer,qt.SIGNAL("timeout()"),self.updateStates) + + # Initialize the source + sourcename = self.dialog.getSourcePluginName() + sourceimpl = self.dialog.getSourcePluginImpl() + self.textbrowser.append("Source: %s" % sourcename) + if sourceimpl == None: + raise "No such source." + try: + sourceimpl.init(copierer) + + # Initialize the destination + destinationname = self.dialog.getDestinationPluginName() + destinationimpl = self.dialog.getDestinationPluginImpl() + self.textbrowser.append("<hr>Destination: %s" % destinationname) + if destinationimpl == None: + raise "No such destination." + try: + destinationimpl.init(copierer) + + self.starttime.start() + self.updatetimer.start(500) + qt.qApp.processEvents() + + # Copy the records + self.textbrowser.append("<hr><i>Copy the records...</i>") + while True: + record = sourceimpl.read() + if record == None: break + destinationimpl.write(record) + + self.updateStates() + finally: + destinationimpl.finish() + finally: + sourceimpl.finish() + + self.setCaption("Copy done") + self.textbrowser.append("<hr><b>Copy done.</b>") + except: + self.setCaption("Copy failed") + self.textbrowser.append("<b>Error: %s</b>" % sys.exc_info()[0]) + import traceback + print "".join( traceback.format_exception(sys.exc_info()[0],sys.exc_info()[1],sys.exc_info()[2]) ) + #self.progressbar.setEnabled(False) + self.donebtn.setEnabled(True) + self.cancelbtn.setEnabled(False) + self.updatetimer.stop() + self.starttime = None + + def show(self): + qt.QDialog.show(self) + qt.QTimer.singleShot(10,self.startCopy) + qt.qApp.processEvents() + + def closeEvent(self, closeevent): + if not self.dialog.getSourcePluginImpl().isFinished(): + if qt.QMessageBox.warning(self,"Abort?","Abort the copy?",qt.QMessageBox.Yes,qt.QMessageBox.No) != qt.QMessageBox.Yes: + closeevent.ignore() + return + self.dialog.getSourcePluginImpl().finish() + self.dialog.getDestinationPluginImpl().finish() + closeevent.accept() + + #-------------------------------------------------------------------- + + class DataSelector(qt.QVGroupBox): + def __init__(self, plugintype, title, caption, parent, dialog, items): + self.plugintype = plugintype + self.pluginimpl = None + self.dialog = dialog + self.mainbox = None + + qt.QVGroupBox.__init__(self,title,parent) + self.setInsideMargin(6) + self.setInsideSpacing(0) + + typebox = qt.QHBox(self) + label = qt.QLabel(caption,typebox) + self.combobox = qt.QComboBox(typebox) + for item in items: + self.combobox.insertItem(str(item)) + label.setBuddy(self.combobox) + typebox.setStretchFactor(self.combobox,1) + + self.scrollview = qt.QScrollView(self) + try: + self.scrollview.setResizePolicy(qt.QScrollView.AutoOne) + self.scrollview.setFrameStyle(qt.QFrame.NoFrame); + self.scrollview.setResizePolicy(qt.QScrollView.AutoOneFit); + self.scrollview.viewport().setPaletteBackgroundColor(self.paletteBackgroundColor()) + except: + import traceback + print "".join( traceback.format_exception(sys.exc_info()[0],sys.exc_info()[1],sys.exc_info()[2]) ) + qt.QObject.connect(self.combobox, qt.SIGNAL("activated(int)"), self.activated) + + def updatePlugin(self): + print "DataSelector.updatePlugin" + self.pluginimpl = None + text = str(self.combobox.currentText()) + plugin = self.dialog.copycenter.plugins[text] + self.pluginimpl = getattr(plugin, self.plugintype) + + def removeMainBox(self): + if self.mainbox == None: return + try: + self.scrollview.removeChild(self.mainbox) + self.mainbox.destroy() + except: + pass + self.mainbox = None + + def updateMainBox(self): + print "DataSelector.updateMainBox" + self.removeMainBox() + self.mainbox = qt.QVBox( self.scrollview.viewport() ) + self.mainbox.setSpacing(2) + if self.pluginimpl != None: + try: + self.pluginimpl.createWidget(self.dialog, self.mainbox) + except: + import traceback + print "".join( traceback.format_exception(sys.exc_info()[0],sys.exc_info()[1],sys.exc_info()[2]) ) + self.mainbox.setStretchFactor(qt.QWidget(self.mainbox), 1) + self.mainbox.show() + self.scrollview.addChild(self.mainbox) + + def activated(self, **args): + self.updatePlugin() + self.updateMainBox() + + def maybeUpdate(self): + print "DataSelector.maybeUpdate" + self.removeMainBox() + qt.QTimer.singleShot(50, self.activated) + + def maybeDone(self): + print "DataSelector.maybeDone" + if self.pluginimpl.widget == None: return + for optionname in self.pluginimpl.options: + self.pluginimpl.options[optionname] = self.pluginimpl.widget.getOptionValue(optionname) + + #-------------------------------------------------------------------- + + class Dialog(qt.QDialog): + def __init__(self, copycenter, parent): + self.copycenter = copycenter + + import qt + import os + import sys + + self.ListViewDialog = ListViewDialog + qt.QDialog.__init__(self, parent, "Dialog", 1, qt.Qt.WDestructiveClose) + self.setCaption("Copy Center") + layout = qt.QVBoxLayout(self) + box = qt.QVBox(self) + box.setMargin(6) + box.setSpacing(6) + layout.addWidget(box) + self.tab = qt.QTabWidget(box) + self.tab.setMargin(6) + box.setStretchFactor(self.tab,1) + + self.jobsbox = CopyJobWidget(self,self.tab) + self.tab.addTab(self.jobsbox,"Jobs") + + self.splitter = qt.QSplitter(self.tab) + + sourceplugins = [] + destinationplugins = [] + for pluginname in self.copycenter.plugins: + if self.copycenter.plugins[pluginname].source != None: + sourceplugins.append(pluginname) + if self.copycenter.plugins[pluginname].destination != None: + destinationplugins.append(pluginname) + sourceplugins.sort() + destinationplugins.sort() + + self.sourcedata = DataSelector( + "source", # id + "Read Data From", # title + "Source:", # caption + self.splitter, self, sourceplugins) + self.destinationdata = DataSelector( + "destination", # id + "Write Data to", # title + "Destination:", # caption + self.splitter, self, destinationplugins) + + btnbox = qt.QHBox(box) + btnbox.setSpacing(6) + okbtn = qt.QPushButton(btnbox) + okbtn.setText("Start Copy") + okbtn.setDefault(True) + qt.QObject.connect(okbtn,qt.SIGNAL("clicked()"),self.startCopy) + cancelbtn = qt.QPushButton(btnbox) + cancelbtn.setText("Cancel") + qt.QObject.connect(cancelbtn,qt.SIGNAL("clicked()"),self.close) + + self.tab.addTab(self.splitter,"Copy") + self.tab.setCurrentPage(1) + + self.helpbrowser = qt.QTextBrowser(self.tab) + self.helpbrowser.setLinkUnderline(False) + self.helpbrowser.setUndoRedoEnabled(False) + self.tab.addTab(self.helpbrowser,"Help") + qt.QObject.connect(self.tab,qt.SIGNAL("currentChanged(QWidget*)"),self.currentTabChanged) + + box.setMinimumSize( qt.QSize(760,500) ) + + defaultfile = os.path.join(self.copycenter.homepath,"default.copycenterjob.xml") + if os.path.isfile(defaultfile): + print "Reading default copy job file: %s" % defaultfile + self.jobsbox.jobfilecombobox.setCurrentText(defaultfile) + + def getSourcePluginName(self): + return str(self.sourcedata.combobox.currentText()) + def getSourcePluginImpl(self): + return self.copycenter.plugins[self.getSourcePluginName()].source + def getDestinationPluginName(self): + return str(self.destinationdata.combobox.currentText()) + def getDestinationPluginImpl(self): + return self.copycenter.plugins[self.getDestinationPluginName()].destination + + def currentTabChanged(self,widget): + if self.tab.currentPage() == self.jobsbox: + # The "Copy" page is done + self.sourcedata.maybeDone() + self.destinationdata.maybeDone() + # Update the "Jobs" page + self.jobsbox.maybeUpdate() + elif self.tab.currentPage() == self.splitter: + # Update the "Copy" page + self.sourcedata.maybeUpdate() + self.destinationdata.maybeUpdate() + elif self.tab.currentPage() == self.helpbrowser and self.helpbrowser.lines() <= 1: + # Update the "Help" page + import os + file = os.path.join(self.copycenter.scriptpath, "readme.html") + if not os.path.isfile(file): return + fh = open(file,'r') + self.helpbrowser.setText( fh.read() ) + fh.close() + + def startCopy(self): + dlg = ProgressDialog(self) + dlg.show() + + #-------------------------------------------------------------------- + + if name == "__main__": + qtapp = qt.QApplication(sys.argv) + else: + qtapp = qt.qApp + dialog = Dialog(copycenter, qtapp.mainWidget()) + dialog.exec_loop() + +import os + +if __name__ == "__main__": + scriptpath = os.getcwd() +else: + scriptpath = os.path.dirname(__name__) + +copycenter = CopyCenter(scriptpath) +runGuiApp(copycenter, __name__) diff --git a/kexi/plugins/scripting/scripts/copycenter/CopyCenter.rc b/kexi/plugins/scripting/scripts/copycenter/CopyCenter.rc new file mode 100644 index 00000000..e3e758b4 --- /dev/null +++ b/kexi/plugins/scripting/scripts/copycenter/CopyCenter.rc @@ -0,0 +1,10 @@ +<KrossScripting> + <ScriptAction + name="CopyCenter" + version="1" + text="Copy Center" + description="Copy data from one source to another." + icon="editcopy" + interpreter="python" + file="CopyCenter.py" /> +</KrossScripting> diff --git a/kexi/plugins/scripting/scripts/copycenter/CopyCenterPluginKexiDB.py b/kexi/plugins/scripting/scripts/copycenter/CopyCenterPluginKexiDB.py new file mode 100644 index 00000000..e8241405 --- /dev/null +++ b/kexi/plugins/scripting/scripts/copycenter/CopyCenterPluginKexiDB.py @@ -0,0 +1,646 @@ +""" +CopyCenterPlugin to provide 'KexiDB'. + +Description: +This python-script is a plugin for the CopyCenter.py. + +Author: +Sebastian Sauer <mail@dipe.org> + +Copyright: +GPL v2 or higher. +""" + +class CopyCenterPlugin: + """ The CopyCenterPlugin to provide abstract access to the 'KexiDB' + framework to CopyCenter.py """ + + name = "Kexi Database" + """ The name this plugin has. The name should be unique and + will be used for displaying a caption. """ + + class Plugin: + """ The implementation of a plugin which is published to the + CopyCenter.py script. While there exists only one instance of + the CopyCenterPlugin class, there will be n instances of this + Plugin class (one for 'source' aka read-data-from and one for + 'destination' aka write-data-to) created from within the + CopyCenter.py. The Source and Destination classes are extending + this Plugin class with specialized functionality. """ + def __init__(self,copycenterplugin): + """ Constructor. """ + self.copycenterplugin = copycenterplugin + self.widget = None + self.options = { + 'autoconnect' : False, + 'project' : '', #'~/test.kexi', + 'driver' : '', #'MySQL', #'SQLite3','MySQL',... + 'file' : '', #'/path/to/mysqlite3dbfile.kexi' + 'hostname' : '127.0.0.1', + 'port' : '', + 'usesocketfile' : False, + 'socketfile' : '', + 'username' : '', + 'password' : '', + 'database' : '', + 'table' : '', + 'fields' : '', + 'where' : '', + } + self.connection = copycenterplugin.Connection(self) + def isFinished(self): + return self.connection.isFinished() + def finish(self): + """ Called if reading is finished.""" + self.connection.finish() + def createWidget(self, dialog, parent): + """ Create and return a widget to modify the plugin settings. """ + return self.copycenterplugin.createWidget(dialog, self, parent) + + class Source(Plugin): + """ Specialization of the Plugin class to implement the + 'source' aka read-data-from functionality. """ + plugintype = "Source" + def init(self,copierer): + """ Called if reading should be initialize. """ + self.connection.init(copierer) + self.connection.initRead() + # Called if a record should be readed. + self.read = self.connection.readRecord + + class Destination(Plugin): + """ Specialization of the Plugin class to implement the + 'destination' aka write-data-to functionality. """ + plugintype = "Destination" + def init(self,copierer): + """ Called if writing should be initialize. """ + self.connection.init(copierer) + self.connection.initWrite() + # Called if a record should be written. + self.write = self.connection.writeRecord + + class Connection: + """ Abstract access to work with KexiDB. """ + def __init__(self,plugin): + self.plugin = plugin + self.copierer = None + self.kexidbconnection = None + self.kexidbcursor = None + self.tableschema = None + self.fieldlist = None + def lastError(self): return self.kexidbconnection.lastError() + def connect(self): return self.kexidbconnection.connect() + def disconnect(self): + if self.kexidbconnection == None or self.kexidbconnection.disconnect(): + self.kexidbconnection = None + return True + return False + def isConnected(self): return self.kexidbconnection != None and self.kexidbconnection.isConnected() + def tableNames(self): return self.kexidbconnection.tableNames() + def hasTableName(self,tablename): return tablename in self.kexidbconnection.tableNames() + def tableSchema(self,tablename): return self.kexidbconnection.tableSchema(tablename) + + def init(self,copierer): + self.copierer = copierer + if self.kexidbconnection == None: + if self.plugin.widget == None: + raise "No connection established." + self.copierer.appendProgressMessage("<i>Trying to connect...</i>") + if self.plugin.widget.driverbox.driver == None: + raise "Invalid driver." + if not self.plugin.widget.connectClicked(): + raise "Failed to connect." + connectiondata = self.kexidbconnection.data() + self.copierer.appendProgressMessage("Connected: %s %s" % (connectiondata.driverName(),connectiondata.serverInfoString())) + + tablename = str(self.plugin.widget.tablebox.tableedit.text()) + if tablename == "": + raise "No table defined" + fields = [ f.strip() for f in str(self.plugin.widget.fieldbox.fieldsedit.text()).split(",") if len(f) > 0 ] + if len(fields) < 1: + raise "No fields defined" + + self.tableschema = self.kexidbconnection.tableSchema(tablename) + if not self.tableschema: raise "No such tableschema \"%s\"" % tablename + self.copierer.appendProgressMessage("Table: %s" % self.tableschema.name()) + + if len(fields) == 1 and fields[0] == "*": + self.fieldlist = self.tableschema.fieldlist() + else: + self.fieldlist = self.tableschema.fieldlist().subList(fields) + if not self.fieldlist: raise "No such fields \"%s\"" % fields + fieldlistnames = self.fieldlist.names() + if len(fieldlistnames) < 1: raise "No valid fields defined for \"%s\"" % fields + self.copierer.appendProgressMessage("Fields: %s" % fieldlistnames) + def finish(self): + if self.plugin.widget == None: + self.disconnect() + else: + self.plugin.widget.disconnectClicked() + self.kexidbcursor = None + self.kexidbconnection = None + self.tableschema = None + self.fieldlist = None + self.copierer = None + def isFinished(self): + return self.copierer == None + + def initRead(self): + print "Initialize read" + #queryschema = self.plugin.copycenterplugin.drivermanager.querySchema() + queryschema = self.tableschema.query() + queryschema.fieldlist().setFields(self.fieldlist) + print "QuerySchema: %s" % queryschema.fieldlist().names() + + whereexpression = str(self.plugin.widget.whereedit.text()) + if whereexpression != "": + print "WHERE-expression: %s" % whereexpression + if not queryschema.setWhereExpression(whereexpression): + raise "Invalid WHERE-expression." + + #print "QuerySchema statement=%s" % queryschema.statement() + self.kexidbcursor = self.kexidbconnection.executeQuerySchema(queryschema) + if not self.kexidbcursor: + raise "Failed to create cursor." + if not self.kexidbcursor.moveFirst(): + raise "The cursor has no records to read from." + + def readRecord(self): + if self.kexidbcursor == None or self.kexidbcursor.eof(): + return None + record = [] + for i in range( self.kexidbcursor.fieldCount() ): + record.append( self.kexidbcursor.value(i) ) + self.kexidbcursor.moveNext() + #print "read record: %s" % record + return record + + def initWrite(self): + print "Initialize write" + + def writeRecord(self,record): + print "write record: %s" % record + if self.kexidbconnection.insertRecord(self.fieldlist,record): + print "=> insert successfully" + self.copierer.writeSuccess(record, 1) + else: + print "=> insert failed: %s" % self.kexidbconnection.lastError() + self.copierer.writeFailed(record) + #import time + #time.sleep(1) + return True + + def __init__(self, copycenter): + """ Constructor. """ + import krosskexidb + self.drivermanager = krosskexidb.DriverManager() + self.copycenter = copycenter + + def createWidget(self, dialog, plugin, parent): + """ Each plugin may provide a qt.QWidget back to the + CopyCenter.py. The widget will be used to configure our + plugin settings. """ + + import qt + import os + import re + + self.dialog = dialog + self.mainbox = None + class ProjectBox(qt.QHBox): + def __init__(self,main,copycenterplugin,plugin,parent): + self.main = main + self.copycenterplugin = copycenterplugin + self.plugin = plugin + + qt.QHBox.__init__(self,parent) + prjlabel = qt.QLabel("Project File:",self) + self.prjcombo = qt.QComboBox(self) + self.prjcombo.setEditable(True) + self.prjcombo.insertItem("") + + path = copycenterplugin.copycenter.homepath + for f in os.listdir(path): + file = os.path.join(path,f) + if os.path.isfile(file) and re.search(".+\\.(kexi|kexis|kexic)$",f): + self.prjcombo.insertItem(os.path.join("~",f)) + + prjlabel.setBuddy(self.prjcombo) + prjsavebtn = qt.QPushButton("...",self) + qt.QObject.connect(prjsavebtn, qt.SIGNAL("clicked()"),self.buttonClicked) + qt.QObject.connect(self.prjcombo, qt.SIGNAL("textChanged(const QString&)"), self.main.projectChanged) + self.setStretchFactor(self.prjcombo,1) + def buttonClicked(self): + text = str(self.prjcombo.currentText()) + if text == "": + text = self.copycenterplugin.copycenter.homepath + elif re.search("^\\~(\\/|\\\\)",text): + import os + text = os.path.join(self.copycenterplugin.copycenter.homepath,text[2:]) + if self.plugin.plugintype == "Source": + filename = qt.QFileDialog.getOpenFileName(text,"*.kexi *.kexis *.kexic;;*",self.copycenterplugin.dialog) + else: # "Destination": + filename = qt.QFileDialog.getSaveFileName(text,"*.kexi *.kexis *.kexic;;*",self.copycenterplugin.dialog) + if str(filename) != "": self.prjcombo.setCurrentText(str(filename)) + + class DriverBox(qt.QVBox): + def __init__(self,main,parent): + qt.QVBox.__init__(self,parent) + self.main = main + self.copycenterplugin = main.copycenterplugin + self.plugin = main.plugin + self.driver = None + + driverbox = qt.QHBox(self) + driverlabel = qt.QLabel("Driver:",driverbox) + self.drivercombo = qt.QComboBox(driverbox) + self.drivercombo.insertItem("") + for driver in self.copycenterplugin.drivermanager.driverNames(): + self.drivercombo.insertItem(driver) + + qt.QObject.connect(self.drivercombo, qt.SIGNAL("activated(int)"), self.activated) + driverlabel.setBuddy(self.drivercombo) + driverbox.setStretchFactor(self.drivercombo,1) + + self.box = qt.QVBox(self) + self.mainbox = None + + def activated(self,index): + drivertext = str(self.drivercombo.currentText()) + + self.box.hide() + if self.mainbox: + self.mainbox.hide() + self.mainbox.destroy() + self.mainbox = None + if index == 0 or drivertext == "": + self.driver = None + self.box.show() + self.main.updateConnectButtons() + return False + + self.driver = self.copycenterplugin.drivermanager.driver(drivertext) + + mainbox = qt.QVBox(self.box) + mainbox.setSpacing(2) + + if self.driver.isFileDriver(): + filebox = qt.QHBox(mainbox) + filelabel = qt.QLabel("File:",filebox) + self.fileedit = qt.QLineEdit(self.plugin.options['file'],filebox) + filelabel.setBuddy(self.fileedit) + filebox.setStretchFactor(self.fileedit,1) + filebtn = qt.QPushButton("...",filebox) + qt.QObject.connect(filebtn, qt.SIGNAL("clicked()"), self.fileClicked) + else: + hostbox = qt.QHBox(mainbox) + hostlabel = qt.QLabel("Hostname:",hostbox) + self.hostedit = qt.QLineEdit(self.plugin.options['hostname'],hostbox) + hostlabel.setBuddy(self.hostedit) + hostbox.setStretchFactor(self.hostedit,1) + + portbox = qt.QHBox(mainbox) + portlabel = qt.QLabel("Port:",portbox) + self.portedit = qt.QLineEdit(self.plugin.options['port'],portbox) + portlabel.setBuddy(self.portedit) + portbox.setStretchFactor(self.portedit,1) + + sockbox = qt.QHBox(mainbox) + self.sockfilecheckbox = qt.QCheckBox("Socket File:",sockbox) + qt.QObject.connect(self.sockfilecheckbox, qt.SIGNAL("toggled(bool)"), self.sockfilecheckboxClicked) + self.sockfilebox = qt.QHBox(sockbox) + self.sockfileedit = qt.QLineEdit(self.plugin.options['socketfile'],self.sockfilebox) + self.sockfilebox.setEnabled(False) + sockfilebtn = qt.QPushButton("...",self.sockfilebox) + self.sockfilecheckbox.setChecked( str(self.plugin.options['usesocketfile']) == str(True) ) + qt.QObject.connect(sockfilebtn, qt.SIGNAL("clicked()"), self.sockfileClicked) + self.sockfilebox.setStretchFactor(self.sockfileedit,1) + sockbox.setStretchFactor(self.sockfilebox,1) + + userbox = qt.QHBox(mainbox) + userlabel = qt.QLabel("Username:",userbox) + self.useredit = qt.QLineEdit(self.plugin.options['username'],userbox) + userlabel.setBuddy(self.useredit) + userbox.setStretchFactor(self.useredit,1) + + passbox = qt.QHBox(mainbox) + passlabel = qt.QLabel("Password:",passbox) + self.passedit = qt.QLineEdit(self.plugin.options['password'],passbox) + self.passedit.setEchoMode(qt.QLineEdit.Password) + passlabel.setBuddy(self.passedit) + passbox.setStretchFactor(self.passedit,1) + + dbbox = qt.QHBox(mainbox) + dblabel = qt.QLabel("Database:",dbbox) + self.dbedit = qt.QLineEdit(self.plugin.options['database'],dbbox) + dblabel.setBuddy(self.dbedit) + dbbox.setStretchFactor(self.dbedit,1) + #self.tablecombo.setText("") + + self.mainbox = mainbox + self.mainbox.show() + self.box.show() + self.main.updateConnectButtons() + return True + + def fileClicked(self): + text = str(self.fileedit.text()) + if text == "": text = self.copycenterplugin.copycenter.homepath + if self.plugin.plugintype == "Source": + filename = qt.QFileDialog.getOpenFileName(text,"*",self.copycenterplugin.dialog) + else: # "Destination": + filename = qt.QFileDialog.getSaveFileName(text,"*",self.copycenterplugin.dialog) + if str(filename) != "": self.fileedit.setText(str(filename)) + def sockfilecheckboxClicked(self,checked): + self.sockfilebox.setEnabled(checked) + + def sockfileClicked(self): + text = str(self.sockfileedit.text()) + if text == "": text = self.copycenterplugin.copycenter.homepath + if self.plugin.plugintype == "Source": + filename = qt.QFileDialog.getOpenFileName(text,"*",self.copycenterplugin.dialog) + else: # "Destination": + filename = qt.QFileDialog.getSaveFileName(text,"*",self.copycenterplugin.dialog) + if str(filename) != "": self.sockfileedit.setText(str(filename)) + + class TableBox(qt.QHBox): + def __init__(self,copycenterplugin,plugin,parent): + qt.QHBox.__init__(self,parent) + self.copycenterplugin = copycenterplugin + self.plugin = plugin + tablelabel = qt.QLabel("Table:",self) + self.tableedit = qt.QLineEdit(self.plugin.options['table'],self) + self.tablebtn = qt.QPushButton("...",self) + self.tablebtn.setEnabled(False) + qt.QObject.connect(self.tablebtn, qt.SIGNAL("clicked()"), self.buttonClicked) + tablelabel.setBuddy(self.tableedit) + self.setStretchFactor(self.tableedit,1) + def buttonClicked(self): + ListViewDialog = self.copycenterplugin.dialog.ListViewDialog + class TableDialog(ListViewDialog): + def __init__(self,tablebox): + ListViewDialog.__init__(self,tablebox,"Tables") + self.mainwidget = tablebox + self.listview.addColumn("Name") + text = str(self.mainwidget.tableedit.text()) + item = None + for table in self.mainwidget.plugin.connection.tableNames(): + if item == None: + item = qt.QListViewItem(self.listview,table) + else: + item = qt.QListViewItem(self.listview,item,table) + if table == text: + self.listview.setSelected(item,True) + self.listview.ensureItemVisible(item) + qt.QObject.connect(self.listview, qt.SIGNAL("doubleClicked(QListViewItem*, const QPoint&, int)"), self.okClicked) + qt.QObject.connect(self.okbtn, qt.SIGNAL("clicked()"), self.okClicked) + def okClicked(self): + item = self.listview.selectedItem() + if item == None: + self.mainwidget.tableedit.setText("") + else: + self.mainwidget.tableedit.setText(item.text(0)) + self.close() + dialog = TableDialog(self) + dialog.show() + + class FieldBox(qt.QHBox): + def __init__(self,copycenterplugin,plugin,parent): + qt.QHBox.__init__(self,parent) + self.copycenterplugin = copycenterplugin + self.plugin = plugin + self.tablename = "" + fieldslabel = qt.QLabel("Fields:",self) + self.fieldsedit = qt.QLineEdit(self.plugin.options['fields'],self) + self.setStretchFactor(self.fieldsedit,1) + fieldslabel.setBuddy(self.fieldsedit) + self.fieldsbtn = qt.QPushButton("...",self) + self.fieldsbtn.setEnabled(False) + qt.QObject.connect(self.fieldsbtn, qt.SIGNAL("clicked()"), self.fieldsClicked) + def fieldsClicked(self): + ListViewDialog = self.copycenterplugin.dialog.ListViewDialog + class FieldsDialog(ListViewDialog): + def __init__(self, fieldbox): + ListViewDialog.__init__(self,fieldbox,"Fields") + self.fieldbox = fieldbox + self.listview.setSelectionMode(qt.QListView.Multi) + self.listview.setSorting(-1) + self.listview.header().setClickEnabled(False) + self.listview.addColumn("Name") + self.listview.addColumn("Type") + self.listview.addColumn("Options") + fieldslist = str(self.fieldbox.fieldsedit.text()).split(",") + allfields = ("*" in fieldslist) + tableschema = self.fieldbox.plugin.connection.tableSchema(self.fieldbox.tablename) + item = None + for field in tableschema.fieldlist().fields(): + opts = [] + for opt in ("isAutoInc","isNotNull","isNotEmpty"): + if getattr(field,opt)(): + opts.append(opt[2:]) + item = self.addItem(( field.name(),field.type(),",".join(opts) ),item) + if allfields or field.name() in fieldslist: + self.listview.setSelected(item,True) + qt.QObject.connect(self.okbtn, qt.SIGNAL("clicked()"), self.okClicked) + def okClicked(self): + selitems = [] + item = self.listview.firstChild() + while item: + if item.isSelected(): + selitems.append(str(item.text(0))) + item = item.nextSibling() + self.fieldbox.fieldsedit.setText(",".join(selitems)) + self.close() + dialog = FieldsDialog(self) + dialog.show() + def tableChanged(self, text): + self.tablename = str(text) + if self.plugin.connection.isConnected(): + if self.plugin.connection.hasTableName(self.tablename): + self.fieldsbtn.setEnabled(True) + return + self.fieldsbtn.setEnabled(False) + + class MainBox(qt.QHBox): + def __init__(self,copycenterplugin,plugin,parent): + qt.QHBox.__init__(self,parent) + self.copycenterplugin = copycenterplugin + self.plugin = plugin + + self.prjbox = ProjectBox(self,copycenterplugin,plugin,parent) + self.driverbox = DriverBox(self,parent) + + statusbar = qt.QHBox(parent) + statusbar.setSpacing(2) + #self.statuslabel = qt.QLabel("Disconnected",statusbar) + #statusbar.setStretchFactor(self.statuslabel,1) + statusbar.setStretchFactor(qt.QWidget(statusbar),1) + self.connectbtn = qt.QPushButton("Connect",statusbar) + self.connectbtn.setEnabled(False) + qt.QObject.connect(self.connectbtn, qt.SIGNAL("clicked()"),self.connectClicked) + self.disconnectbtn = qt.QPushButton("Disconnect",statusbar) + self.disconnectbtn.setEnabled(False) + qt.QObject.connect(self.disconnectbtn, qt.SIGNAL("clicked()"),self.disconnectClicked) + + #self.connectionbox = ConnectionBox(copycenterplugin,plugin,parent) + self.tablebox = TableBox(copycenterplugin,plugin,parent) + self.fieldbox = FieldBox(copycenterplugin,plugin,parent) + qt.QObject.connect(self.tablebox.tableedit, qt.SIGNAL("textChanged(const QString&)"), self.fieldbox.tableChanged) + + if self.plugin.options['project'] != '': + self.prjbox.prjcombo.setCurrentText(self.plugin.options['project']) + + if self.plugin.options['driver'] != '': + try: + item = str(self.driverbox.drivercombo.listBox().findItem(self.plugin.options['driver'],qt.Qt.ExactMatch).text()) + self.driverbox.drivercombo.setCurrentText(item) + self.driverbox.activated(item) + except: + pass + + if self.plugin.plugintype == "Destination": + #typebox = qt.QHBox(parent) + #label = qt.QLabel("Operation:",typebox) + #combobox = qt.QComboBox(typebox) + #combobox.insertItem("Append") + #combobox.insertItem("Replace") + #combobox.insertItem("Update") + #combobox.insertItem("Update/Insert") + #combobox.insertItem("Insert new") + #label.setBuddy(combobox) + #typebox.setStretchFactor(combobox,1) + pass + elif self.plugin.plugintype == "Source": + wherebox = qt.QHBox(parent) + wherelabel = qt.QLabel("Where:",wherebox) + self.whereedit = qt.QLineEdit(self.plugin.options['where'],wherebox) + + #orderbox = qt.QHBox(parent) + #orderlabel = qt.QLabel("Order By:",orderbox) + #orderedit = qt.QLineEdit("",orderbox) + + #errbox = qt.QHBox(parent) + #errlabel = qt.QLabel("On Error:",errbox) + #errcombo = qt.QComboBox(errbox) + #errcombo.insertItem("Ask") + #errcombo.insertItem("Skip") + #errcombo.insertItem("Abort") + #errlabel.setBuddy(errcombo) + #errbox.setStretchFactor(errcombo,1) + + if self.plugin.options['autoconnect']: + self.connectClicked() + + def projectChanged(self, text): + #if self.driverbox.drivercombo.currentItem() != 0: + # self.driverbox.drivercombo.setCurrentItem(0) + + file = str(text) + import os + if re.search("^\\~(\\/|\\\\)",file): + file = os.path.join(self.copycenterplugin.copycenter.homepath,file[2:]) + if file == "" or not os.path.isfile(file): + self.driverbox.drivercombo.setCurrentItem(0) + self.driverbox.activated(0) + return + + connectiondata = self.copycenterplugin.drivermanager.createConnectionDataByFile(file) + if connectiondata == None: + raise "Unsupported file." + + drivername = connectiondata.driverName().lower() + print "driver: %s" % drivername + for i in range(1,self.driverbox.drivercombo.count()): + if drivername == self.driverbox.drivercombo.text(i).lower(): + self.driverbox.drivercombo.setCurrentItem(i) + self.driverbox.activated(i) + break + + if self.driverbox.driver != None: + if self.driverbox.driver.isFileDriver(): + self.driverbox.fileedit.setText(connectiondata.fileName()) + else: # server + self.driverbox.hostedit.setText(connectiondata.hostName()) + self.driverbox.portedit.setText(str(connectiondata.port())) + self.driverbox.sockfilecheckbox.setChecked(connectiondata.localSocketFileUsed()) + self.driverbox.sockfileedit.setText(connectiondata.localSocketFileName()) + self.driverbox.useredit.setText(connectiondata.userName()) + self.driverbox.passedit.setText(connectiondata.password()) + self.driverbox.dbedit.setText(connectiondata.databaseName()) + + def connectClicked(self): + if self.driverbox.driver == None: + print "No driver selected." + return False + connectiondata = self.copycenterplugin.drivermanager.createConnectionData() + if self.driverbox.driver.isFileDriver(): + file = str(self.driverbox.fileedit.text()) + if file == "" or not os.path.isfile(file): + qt.QMessageBox.critical(self,"Failed to connect","There exists no such database file \"%s\"" % file) + return False + connectiondata.setFileName(file) + connectiondata.setDatabaseName(file) + else: + connectiondata.setHostName(str(self.driverbox.hostedit.text())) + connectiondata.setPort(str(self.driverbox.portedit.text())) + connectiondata.setLocalSocketFileUsed(self.driverbox.sockfilecheckbox.isChecked()) + connectiondata.setLocalSocketFileName(str(self.driverbox.sockfileedit.text())) + connectiondata.setPassword(str(self.driverbox.passedit.text())) + connectiondata.setUserName(str(self.driverbox.useredit.text())) + connectiondata.setDatabaseName(str(self.driverbox.dbedit.text())) + print "Creating connection" + connection = self.driverbox.driver.createConnection(connectiondata) + print "Trying to connect" + if not connection.connect(): + qt.QMessageBox.critical(self,"Failed to connect",connection.lastError()) + return False + print "Use database \"%s\"" % connectiondata.databaseName() + if not connection.useDatabase( connectiondata.databaseName() ): + qt.QMessageBox.critical(self,"Failed to connect",connection.lastError()) + return False + print "dbnames = %s" % connection.databaseNames() + print "tablenames = %s" % connection.tableNames() + #self.useDatabase(connection, filename) + + self.plugin.connection.kexidbconnection = connection + self.updateConnectButtons() + return True + + def disconnectClicked(self): + if not self.plugin.connection.disconnect(): + qt.QMessageBox.critical(self,"Failed to disconnect",self.plugin.connection.lastError()) + return + self.updateConnectButtons() + + def updateConnectButtons(self): + connected = self.plugin.connection.isConnected() + self.prjbox.setEnabled(not connected) + self.driverbox.setEnabled(not connected) + self.connectbtn.setEnabled( (not connected) and (self.driverbox.driver != None) ) + self.disconnectbtn.setEnabled(connected) + self.tablebox.tablebtn.setEnabled(connected) + self.fieldbox.tableChanged(self.tablebox.tableedit.text()) + + def getOptionValue(self,optionname): + try: + if optionname == 'project': return str(self.prjbox.prjcombo.currentText()) + elif optionname == 'driver': return str(self.driverbox.drivercombo.currentText()) + elif optionname == 'file': return str(self.driverbox.fileedit.text()) + elif optionname == 'hostname': return str(self.driverbox.hostedit.text()) + elif optionname == 'port': return str(self.driverbox.portedit.text()) + elif optionname == 'usesocketfile': return str(self.driverbox.sockfilecheckbox.isChecked()) + elif optionname == 'socketfile': return str(self.driverbox.sockfileedit.text()) + elif optionname == 'username': return str(self.driverbox.useredit.text()) + elif optionname == 'password': return str(self.driverbox.passedit.text()) + elif optionname == 'database': return str(self.driverbox.dbedit.text()) + elif optionname == 'table': return str(self.tablebox.tableedit.text()) + elif optionname == 'fields': return str(self.fieldbox.fieldsedit.text()) + elif optionname == 'where': return str(self.whereedit.text()) + except: + pass + return "" + + mainbox = MainBox(self,plugin,parent) + plugin.widget = mainbox + return mainbox + diff --git a/kexi/plugins/scripting/scripts/copycenter/CopyCenterPluginQtSQL.py b/kexi/plugins/scripting/scripts/copycenter/CopyCenterPluginQtSQL.py new file mode 100644 index 00000000..985d757d --- /dev/null +++ b/kexi/plugins/scripting/scripts/copycenter/CopyCenterPluginQtSQL.py @@ -0,0 +1,495 @@ +""" +CopyCenterPlugin to provide 'QtSQL'. + +Description: +This python-script is a plugin for the CopyCenter.py. + +Author: +Sebastian Sauer <mail@dipe.org> + +Copyright: +GPL v2 or higher. +""" + +class CopyCenterPlugin: + """ The CopyCenterPlugin to provide 'QtSQL' to CopyCenter.py """ + + name = "QtSQL Database" + """ The name this plugin has. The name should be unique and + will be used for displaying a caption. """ + + class Plugin: + def _init_(self,copycenterplugin): + self.copycenterplugin = copycenterplugin + self.widget = None + self.database = None + self.cursor = None + self.isfinished = True + def _init(self,copierer): + self.copierer = copierer + if not self.widget.connectClicked(): + raise "Failed to connect with database." + if self.database == None or not self.database.isOpen(): + raise "Database is not initialized or not opened." + self.copierer.appendProgressMessage("Connected: %s %s@%s:%i %s" % + (str(self.database.driverName()),str(self.database.userName()),str(self.database.hostName()),self.database.port(),str(self.database.databaseName())) ) + self.isfinished = False + def isFinished(self): + return self.isfinished + def finish(self): + self.isfinished = True + self.widget.disconnectClicked() + def createWidget(self,dialog,parent): + return self.copycenterplugin.widget(dialog, self, parent) + + class Source(Plugin): + plugintype = "Source" + def __init__(self,copycenterplugin): + self._init_(copycenterplugin) + self.options = { + 'driver': 'QMYSQL3', #'QMYSQL3','QPSQL7','QODBC3',... + 'hostname': '127.0.0.1', + 'port': 3306, + 'username': 'root', #'MyUsername', + 'password': '', #'MySecretPassword', + 'database': '', #'MyQtSQLDatabase', + 'table': '', #'table1', + 'fields': '', #'f1,f2', + 'where': '', + } + def init(self,copierer): + self._init(copierer) + tablename = str(self.widget.tableedit.text()) + wherestatement = str(self.widget.whereedit.text()) + import qt + import qtsql + self.cursor = qtsql.QSqlCursor(tablename,True,self.database) + self.cursor.setFilter(wherestatement) + if not self.cursor.select(): + raise "Select on cursor failed.<br>%s<br>%s" % ( str(self.cursor.lastError().driverText()),str(self.cursor.lastError().databaseText()) ) + self.fieldlist = [] + for fieldname in str(self.widget.fieldedit.text()).split(","): + fn = fieldname.strip() + if fn != "": + field = self.cursor.field(fn) + if not field: + raise "There exists no such field \"%s\" in the table \"%s\"." % (fn,tablename) + self.fieldlist.append(str(field.name())) + if len(self.fieldlist) < 1: + raise "No fields for table \"%s\" defined." % tablename + copierer.appendProgressMessage("SQL: %s" % str(self.cursor.executedQuery())) + + def read(self): + if not self.cursor.next(): + return None + record = [] + for fieldname in self.fieldlist: + record.append( unicode(self.cursor.value(fieldname).toString()).encode("latin-1") ) + #print "read record: %s" % record + return record + + class Destination(Plugin): + plugintype = "Destination" + def __init__(self,copycenterplugin): + self._init_(copycenterplugin) + self.options = { + 'driver': 'QMYSQL3', #'QMYSQL3','QPSQL7','QODBC3',... + 'hostname': '127.0.0.1', + 'port': 3306, + 'username': 'root', #'MyUsername', + 'password': '', #'MySecretPassword', + 'database': '', #'MyQtSQLDatabase', + 'table': '', #'table2', + 'fields': '', #'field1,field2', + 'operation': 'Insert', #'Insert','Update'... + 'indexfield': '', + } + def init(self,copierer): + self._init(copierer) + import qt + import qtsql + + self.fieldlist = [] + for fieldname in str(self.widget.fieldedit.text()).split(","): + fn = fieldname.strip() + if fn != "": self.fieldlist.append(fn) + + tablename = str(self.widget.tableedit.text()) + self.cursor = qtsql.QSqlCursor(tablename,True,self.database) + { + 0: self.initInsert, + 1: self.initUpdate + }[ self.widget.operationedit.currentItem() ]() + + def initInsert(self): + self.write = self.writeInsert + if not self.cursor.select(): + raise "Select on cursor failed.<br>%s<br>%s" % ( str(self.cursor.lastError().driverText()),str(self.cursor.lastError().databaseText()) ) + for fieldname in self.fieldlist: # check fieldlist + field = self.cursor.field(fieldname) + if not field: raise "There exists no such field \"%s\" in the table \"%s\"." % (fieldname, self.cursor.name()) + self.copierer.appendProgressMessage("Insert SQL: %s" % str(self.cursor.executedQuery())) + + def writeInsert(self, record): + print "insert record: %s" % record + import qt + cursorrecord = self.cursor.primeInsert() + count = len(record) + for i in range(len(self.fieldlist)): + if i == count: break + r = record[i] + if r == None: + v = qt.QVariant() + else: + v = qt.QVariant(r) + cursorrecord.setValue(self.fieldlist[i], v) + rowcount = self.cursor.insert() + if rowcount < 1: + drv = unicode(self.cursor.lastError().driverText()).encode("latin-1") + db = unicode(self.cursor.lastError().databaseText()).encode("latin-1") + print "failed: %s %s" % (drv,db) + self.copierer.writeFailed(record) + else: + self.copierer.writeSuccess(record,rowcount) + #import time + #time.sleep(1) + return True + + def initUpdate(self): + self.write = self.writeUpdate + self.indexfieldname = str(self.widget.indexedit.text()).strip() + if self.indexfieldname == "": raise "No index-field defined." + pkindex = self.cursor.index(self.indexfieldname) + if not pkindex: raise "Invalid index-field defined." + self.cursor.setPrimaryIndex(pkindex) + #self.cursor.setMode( qtsql.QSqlCursor.Insert | qtsql.QSqlCursor.Update ) + self.copierer.appendProgressMessage("Update SQL: %s" % str(self.cursor.executedQuery())) + + def writeUpdate(self, record): + import qt + # determinate the primary-index + try: + idx = self.fieldlist.index(self.indexfieldname) + indexvalue = record[idx] + except: + import traceback + print "".join( traceback.format_exception(sys.exc_info()[0],sys.exc_info()[1],sys.exc_info()[2]) ) + raise "Failed to determinate the value for the primary key." + # select cursor and go to matching record. + wherestatement = "%s = \"%s\"" % (self.indexfieldname, indexvalue) + if not self.cursor.select(wherestatement): + raise "Select on cursor failed.<br>%s<br>%s" % ( str(self.cursor.lastError().driverText()),str(self.cursor.lastError().databaseText()) ) + if not self.cursor.next(): + #print "No such record to update !" + return False + # Prepare updating the record. + cursorrecord = self.cursor.primeUpdate() + # Update the fields in the record. + count = len(record) + for i in range(len(self.fieldlist)): + if i == count: break + fieldname = self.fieldlist[i] + if self.indexfieldname != fieldname: # don't update the indexfield! + r = record[i] + if r == None: + v = qt.QVariant() + else: + v = qt.QVariant(r) + cursorrecord.setValue(fieldname, v) + # Write updated record. + rowcount = self.cursor.update() + if rowcount < 1: + self.copierer.writeFailed(record) + else: + self.copierer.writeSuccess(record,rowcount) + print "updated record (rowcount %s): %s" % (rowcount,record) + return True + + def __init__(self, copycenter): + """ Constructor. """ + pass + + def widget(self,dialog,plugin,parent): + """ Each plugin may provide a qt.QWidget back to the + CopyCenter.py. The widget will be used to configure our + plugin settings. """ + + import qt + import os + + self.dialog = dialog + ListViewDialog = self.dialog.ListViewDialog + class TableDialog(ListViewDialog): + def __init__(self, mainwidget): + ListViewDialog.__init__(self,mainwidget,"Tables") + self.mainwidget = mainwidget + self.listview.addColumn("Name") + text = str(self.mainwidget.tableedit.text()) + item = None + for table in self.mainwidget.plugin.database.tables(): + if item == None: + item = qt.QListViewItem(self.listview,table) + else: + item = qt.QListViewItem(self.listview,item,table) + if table == text: + self.listview.setSelected(item,True) + self.listview.ensureItemVisible(item) + qt.QObject.connect(self.listview, qt.SIGNAL("doubleClicked(QListViewItem*, const QPoint&, int)"), self.okClicked) + qt.QObject.connect(self.okbtn, qt.SIGNAL("clicked()"), self.okClicked) + def okClicked(self): + item = self.listview.selectedItem() + if item == None: + self.mainwidget.tableedit.setText("") + else: + self.mainwidget.tableedit.setText(item.text(0)) + self.close() + + class FieldsDialog(ListViewDialog): + def __init__(self, mainwidget): + ListViewDialog.__init__(self,parent,"Fields") + self.mainwidget = mainwidget + self.listview.setSelectionMode(qt.QListView.Multi) + self.listview.setSorting(-1) + self.listview.header().setClickEnabled(False) + self.listview.addColumn("Name") + self.listview.addColumn("Type") + self.listview.addColumn("Options") + tablename = str(self.mainwidget.tableedit.text()) + recinfo = self.mainwidget.plugin.database.recordInfo(tablename) + if recinfo != None: + fieldslist = str(self.mainwidget.fieldedit.text()).split(",") + allfields = ("*" in fieldslist) + item = None + for fieldinfo in recinfo: + opts = "" + for s in ('Required','Calculated'): #,'Generated'): + if getattr(fieldinfo,"is%s" % s)(): opts += "%s " % s + item = self.addItem((fieldinfo.name(), qt.QVariant.typeToName(fieldinfo.type()), opts),item) + if allfields or fieldinfo.name() in fieldslist: + self.listview.setSelected(item,True) + qt.QObject.connect(self.okbtn, qt.SIGNAL("clicked()"), self.okClicked) + def okClicked(self): + selitems = [] + item = self.listview.firstChild() + while item: + if item.isSelected(): + selitems.append(str(item.text(0))) + item = item.nextSibling() + self.mainwidget.fieldedit.setText(",".join(selitems)) + self.close() + + + class MainWidget(qt.QHBox): + def __init__(self,plugin,dialog,parent): + import qt + import qtsql + qt.QHBox.__init__(self,parent) + self.dialog = dialog + self.plugin = plugin + + self.connectionbox = qt.QVBox(parent) + self.connectionbox.setSpacing(2) + + driverbox = qt.QHBox(self.connectionbox) + driverlabel = qt.QLabel("Driver:",driverbox) + self.driveredit = qt.QComboBox(driverbox) + for driver in qtsql.QSqlDatabase.drivers(): + self.driveredit.insertItem(driver) + if self.plugin.options['driver'] == driver: + self.driveredit.setCurrentItem(self.driveredit.count() - 1) + driverlabel.setBuddy(self.driveredit) + driverbox.setStretchFactor(self.driveredit,1) + + hostbox = qt.QHBox(self.connectionbox) + hostlabel = qt.QLabel("Hostname:",hostbox) + self.hostedit = qt.QLineEdit(self.plugin.options['hostname'],hostbox) + hostlabel.setBuddy(self.hostedit) + hostbox.setStretchFactor(self.hostedit,1) + + portbox = qt.QHBox(self.connectionbox) + portlabel = qt.QLabel("Port:",portbox) + self.portedit = qt.QLineEdit(str(self.plugin.options['port']),portbox) + portlabel.setBuddy(self.portedit) + portbox.setStretchFactor(self.portedit,1) + + userbox = qt.QHBox(self.connectionbox) + userlabel = qt.QLabel("Username:",userbox) + self.useredit = qt.QLineEdit(self.plugin.options['username'],userbox) + userlabel.setBuddy(self.useredit) + userbox.setStretchFactor(self.useredit,1) + + passbox = qt.QHBox(self.connectionbox) + passlabel = qt.QLabel("Password:",passbox) + self.passedit = qt.QLineEdit(self.plugin.options['password'],passbox) + self.passedit.setEchoMode(qt.QLineEdit.Password) + passlabel.setBuddy(self.passedit) + passbox.setStretchFactor(self.passedit,1) + + dbbox = qt.QHBox(self.connectionbox) + dblabel = qt.QLabel("Database:",dbbox) + self.dbedit = qt.QLineEdit(self.plugin.options['database'],dbbox) + dblabel.setBuddy(self.dbedit) + dbbox.setStretchFactor(self.dbedit,1) + + statusbar = qt.QHBox(parent) + statusbar.setSpacing(2) + statusbar.setStretchFactor(qt.QWidget(statusbar),1) + self.connectbtn = qt.QPushButton("Connect",statusbar) + qt.QObject.connect(self.connectbtn, qt.SIGNAL("clicked()"),self.connectClicked) + self.disconnectbtn = qt.QPushButton("Disconnect",statusbar) + self.disconnectbtn.setEnabled(False) + qt.QObject.connect(self.disconnectbtn, qt.SIGNAL("clicked()"),self.disconnectClicked) + + tablebox = qt.QHBox(parent) + tablelabel = qt.QLabel("Table:",tablebox) + self.tableedit = qt.QLineEdit(self.plugin.options['table'],tablebox) + qt.QObject.connect(self.tableedit, qt.SIGNAL("textChanged(const QString&)"), self.tableEditChanged) + self.tablebtn = qt.QPushButton("...",tablebox) + self.tablebtn.setEnabled(False) + qt.QObject.connect(self.tablebtn, qt.SIGNAL("clicked()"), self.tableBtnClicked) + tablelabel.setBuddy(self.tableedit) + tablebox.setStretchFactor(self.tableedit,1) + + fieldbox = qt.QHBox(parent) + fieldlabel = qt.QLabel("Fields:",fieldbox) + self.fieldedit = qt.QLineEdit(self.plugin.options['fields'],fieldbox) + self.fieldbtn = qt.QPushButton("...",fieldbox) + self.fieldbtn.setEnabled(False) + qt.QObject.connect(self.fieldbtn, qt.SIGNAL("clicked()"), self.fieldBtnClicked) + fieldlabel.setBuddy(self.fieldedit) + fieldbox.setStretchFactor(self.fieldedit,1) + + if self.plugin.plugintype == "Source": + box = qt.QHBox(parent) + wherelabel = qt.QLabel("Where:",box) + self.whereedit = qt.QLineEdit(self.plugin.options['where'],box) + wherelabel.setBuddy(self.whereedit) + box.setStretchFactor(self.whereedit,1) + elif self.plugin.plugintype == "Destination": + + class OperationBox(qt.QVBox): + def __init__(self, mainwidget, parent): + self.mainwidget = mainwidget + qt.QVBox.__init__(self, parent) + opbox = qt.QHBox(self) + operationlabel = qt.QLabel("Operation:",opbox) + self.mainwidget.operationedit = qt.QComboBox(opbox) + for op in ('Insert','Update'): + self.mainwidget.operationedit.insertItem(op) + if self.mainwidget.plugin.options['operation'] == op: + self.mainwidget.operationedit.setCurrentItem(self.mainwidget.operationedit.count() - 1) + operationlabel.setBuddy(self.mainwidget.operationedit) + opbox.setStretchFactor(self.mainwidget.operationedit,1) + self.box = None + qt.QObject.connect(self.mainwidget.operationedit, qt.SIGNAL("activated(int)"), self.operationActivated) + self.operationActivated() + def operationActivated(self, **args): + if self.box: + self.box.hide() + self.box.destroy() + self.box = None + def showInsert(self): + pass + def showUpdate(self): + self.box = qt.QHBox(self) + indexlabel = qt.QLabel("Indexfield:", self.box) + self.mainwidget.indexedit = qt.QLineEdit(self.mainwidget.plugin.options['indexfield'], self.box) + indexlabel.setBuddy(self.mainwidget.indexedit) + self.box.setStretchFactor(self.mainwidget.indexedit,1) + { + 0: showInsert, + 1: showUpdate, + }[ self.mainwidget.operationedit.currentItem() ](self) + if self.box != None: self.box.show() + OperationBox(self,parent) + + def tableEditChanged(self,text): + if self.plugin.database != None and self.plugin.database.isOpen(): + if str(text) in self.plugin.database.tables(): + self.fieldbtn.setEnabled(True) + return + self.fieldbtn.setEnabled(False) + + def tableBtnClicked(self): + dialog = TableDialog(self) + dialog.show() + + def fieldBtnClicked(self): + dialog = FieldsDialog(self) + dialog.show() + + def updateConnectState(self): + connected = self.plugin.database != None and self.plugin.database.isOpen() + self.connectionbox.setEnabled(not connected) + self.connectbtn.setEnabled(not connected) + self.disconnectbtn.setEnabled(connected) + self.tablebtn.setEnabled(connected) + self.tableEditChanged(self.tableedit.text()) + + def getOptionValue(self,optionname): + try: + if optionname == 'driver': return str(self.driveredit.currentText()) + if optionname == 'hostname': return str(self.hostedit.text()) + if optionname == 'port': return str(self.portedit.text()) + if optionname == 'username': return str(self.useredit.text()) + if optionname == 'password': return str(self.passedit.text()) + if optionname == 'database': return str(self.dbedit.text()) + if optionname == 'table': return str(self.tableedit.text()) + if optionname == 'fields': return str(self.fieldedit.text()) + if optionname == 'where': return str(self.whereedit.text()) + if optionname == 'operation': return str(self.operationedit.currentText()) + if optionname == 'indexfield': return str(self.indexedit.text()) + except: + import traceback + print "".join( traceback.format_exception(sys.exc_info()[0],sys.exc_info()[1],sys.exc_info()[2]) ) + return "" + + def connectClicked(self): + if self.plugin.database != None and self.plugin.database.isOpen(): + print "already connected. not needed to reconnect..." + self.updateConnectState() + return True + print "trying to connect..." + + import qtsql + drivername = str(self.driveredit.currentText()) + print "drivername: %s" % drivername + connectionname = "CopyCenter%s" % self.plugin.plugintype + print "connectionname: %s" % connectionname + self.plugin.database = qtsql.QSqlDatabase.addDatabase(drivername,connectionname) + if not self.plugin.database: + qt.QMessageBox.critical(self,"Failed to connect","<qt>Failed to create database for driver \"%s\"</qt>" % drivername) + return False + + hostname = str(self.hostedit.text()) + self.plugin.database.setHostName(hostname) + + portnumber = int(str(self.portedit.text())) + self.plugin.database.setPort(portnumber) + + username = str(self.useredit.text()) + self.plugin.database.setUserName(username) + + password = str(self.passedit.text()) + self.plugin.database.setPassword(password) + + databasename = str(self.dbedit.text()) + self.plugin.database.setDatabaseName(databasename) + + if not self.plugin.database.open(): + qt.QMessageBox.critical(self,"Failed to connect","<qt>%s<br><br>%s</qt>" % (self.plugin.database.lastError().driverText(),self.plugin.database.lastError().databaseText())) + return False + print "database is opened now!" + self.updateConnectState() + return True + + def disconnectClicked(self): + print "trying to disconnect..." + if self.plugin.database: + self.plugin.database.close() + self.plugin.database = None + print "database is closed now!" + self.updateConnectState() + + plugin.widget = MainWidget(plugin,self.dialog,parent) + return plugin.widget diff --git a/kexi/plugins/scripting/scripts/copycenter/Makefile.am b/kexi/plugins/scripting/scripts/copycenter/Makefile.am new file mode 100644 index 00000000..d46928ed --- /dev/null +++ b/kexi/plugins/scripting/scripts/copycenter/Makefile.am @@ -0,0 +1,4 @@ +include $(top_srcdir)/kexi/Makefile.global + +scriptsdir = $(kde_datadir)/kexi/scripts/copycenter +scripts_SCRIPTS = CopyCenter.py CopyCenterPluginQtSQL.py CopyCenterPluginKexiDB.py CopyCenter.rc readme.html diff --git a/kexi/plugins/scripting/scripts/copycenter/readme.html b/kexi/plugins/scripting/scripts/copycenter/readme.html new file mode 100644 index 00000000..2aff6152 --- /dev/null +++ b/kexi/plugins/scripting/scripts/copycenter/readme.html @@ -0,0 +1,20 @@ +<html><body> +<h1>Copy Center</h1> + +<b>Version 1.2</b> + +<p>Python script to copy data between database backends. The flexible +plugin-architecture allows transparent copies between different backends.</p> + +<ul> +<li>Read+write Kexi Databases. This includes all database backends supported by Kexi (like SQLite, MySQL or PostgreSQL).</li> +<li>Read+write QtSQL Databases. MySQL, PostgreSQL and UnixODBC are supported. There might even be more like Oracle in the commercial Qt version or 3rd party backends.</li> +<li>Runs embedded in Kexi (from the tools=>scripts menu) as well as independent of Kexi (use "krossrunner ~/.kde/share/apps/kexi/scripts/copycenter/CopyCenter.py" or python direct).</li> +<li>Depends only on PyQt. PyKDE is not used at all and Kross (included in KOffice 1.5) is optional.</li> +</ul> + +Author: (C)2006 Sebastian Sauer (mail at dipe dot org)<br> +Website: http://www.kde-files.org/content/show.php?content=35251<br> +License: GPL v2 or higher<br> + +</body></html> diff --git a/kexi/plugins/scripting/scripts/exportxhtml/ExportXHTML.py b/kexi/plugins/scripting/scripts/exportxhtml/ExportXHTML.py new file mode 100644 index 00000000..cace0340 --- /dev/null +++ b/kexi/plugins/scripting/scripts/exportxhtml/ExportXHTML.py @@ -0,0 +1,196 @@ +""" +Export table or query data. + +Description: +This script exports a KexiDB table or query to different fileformats. + +Author: +Sebastian Sauer <mail@dipe.org> + +Copyright: +Dual-licensed under LGPL v2+higher and the BSD license. +""" + +class Datasource: + def __init__(self): + import kexiapp + keximainwindow = kexiapp.get("KexiAppMainWindow") + + try: + self.connection = keximainwindow.getConnection() + except: + raise "No connection established. Please open a project before." + + self.schema = None + + def getSources(self): + sources = [] + for table in self.connection.tableNames(): + sources.append("Tables/%s" % table) + for query in self.connection.queryNames(): + sources.append("Queries/%s" % query) + sources.sort() + return sources + + def setSource(self, source): + s = source.split("/",1) + if s[0] == "Tables": + self.schema = self.connection.tableSchema( s[1] ) + self.queryschema = self.schema.query() + elif s[0] == "Queries": + self.schema = self.connection.querySchema( s[1] ) + self.queryschema = self.schema + self.cursor = None + return self.schema != None + + def name(self): + return self.schema.name() + + def caption(self): + return self.schema.caption() + + def description(self): + return self.schema.description() + + def header(self): + h = [] + for field in self.schema.fieldlist().fields(): + s = field.caption() + if s == None or s == "": + s = field.name() + h.append(s) + return h + + def getNext(self): + if not self.cursor: + self.cursor = self.connection.executeQuerySchema( self.queryschema ) + if not self.cursor: + raise "Failed to execute queryschema." + if not self.cursor.moveFirst(): + raise "Failed to move cursor to first record." + if self.cursor.eof(): + self.cursor = None + return None + items = [] + for i in range( self.cursor.fieldCount() ): + items.append( self.cursor.value(i) ) + self.cursor.moveNext() + return items + +class HtmlExporter: + def __init__(self, datasource): + self.datasource = datasource + + def htmlescape(self, text): + import string + return string.replace(string.replace(string.replace(str(text),'&','&'),'<','<'),'>','>') + + def write(self, output, style): + name = self.datasource.name() + + output.write("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n") + output.write("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">\n") + output.write("<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\">\n") + output.write("<head><title>%s</title>\n" % name) + output.write("<style type=\"text/css\">\n<!--\n") + if style == "Paper": + output.write("html { background-color:#efefef; }") + output.write("body { background-color:#fafafa; color:#303030; margin:1em; padding:1em; border:#606060 1px solid; }") + elif style == "Blues": + output.write("html { background-color:#0000aa; }") + output.write("body { background-color:#000066; color:#efefff; margin:1em; padding:1em; border:#00f 1px solid; }") + output.write("h1 { color:#0000ff; }") + output.write("th { color:#0000aa; }") + else: + output.write("html { background-color:#ffffff; color:#000; }") + output.write("body { margin:1em; }") + output.write("\n//-->\n</style>\n") + output.write("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n") + output.write("</head><body><h1>%s</h1>\n" % name) + + caption = self.datasource.caption() + if caption and caption != name: + output.write("caption: %s<br />\n" % caption) + + description = self.datasource.description() + if description: + output.write("description: %s<br />\n" % description) + + #import datetime + #output.write("date: %s<br />" % datetime.datetime.now()) + + output.write("<table border='1'>\n") + + output.write("<tr>") + for h in self.datasource.header(): + output.write("<th>%s</th>" % h) + output.write("</tr>") + + while 1 == 1: + items = self.datasource.getNext() + if items == None: break + output.write("<tr>") + for item in items: + u = unicode(str(self.htmlescape(item)),"latin-1") + output.write("<td>%s</td>" % u.encode("utf-8")) + output.write("</tr>\n") + output.write("</table>\n") + output.write("</body></html>\n") + +class GuiApp: + def __init__(self, datasource): + self.datasource = datasource + + try: + import gui + except: + raise "Import of the Kross GUI module failed." + + self.dialog = gui.Dialog("Export XHTML") + self.dialog.addLabel(self.dialog, "Export a table- or query-datasource to a XHTML-file.") + + datasourceitems = self.datasource.getSources() + self.datasourcelist = self.dialog.addList(self.dialog, "Datasource:", datasourceitems) + + styleitems = ["Plain", "Paper", "Blues"] + self.stylelist = self.dialog.addList(self.dialog, "Style:", styleitems) + + #queryframe = Tkinter.Frame(frame) + #queryframe.pack() + #Tkinter.Label(queryframe, text="Table or query to export:").pack(side=Tkinter.LEFT) + #self.querycontent = Tkinter.StringVar() + #self.query = apply(Tkinter.OptionMenu, (queryframe, self.querycontent) + tuple( self.datasource.getSources() )) + #self.query.pack(side=Tkinter.LEFT) + + self.file = self.dialog.addFileChooser(self.dialog, + "File:", + gui.getHome() + "/kexidata.xhtml", + (('XHTML files', '*.xhtml'),('All files', '*'))) + + btnframe = self.dialog.addFrame(self.dialog) + self.dialog.addButton(btnframe, "Export", self.doExport) + self.dialog.addButton(btnframe, "Cancel", self.dialog.close) + + self.dialog.show() + + def doExport(self): + file = str( self.file.get() ) + query = str( self.datasourcelist.get() ) + print "Exporting '%s' to file '%s' ..." % (query,file) + + if not self.datasource.setSource(query): + raise "Invalid datasource selected." + #return + + style = str( self.stylelist.get() ) + + f = open(file, "w") + global HtmlExporter + exporter = HtmlExporter(self.datasource) + exporter.write(f, style) + f.close() + + print "Successfully exported '%s' to file %s" % (query,file) + self.dialog.close() + +GuiApp( Datasource() ) diff --git a/kexi/plugins/scripting/scripts/exportxhtml/ExportXHTML.rc b/kexi/plugins/scripting/scripts/exportxhtml/ExportXHTML.rc new file mode 100644 index 00000000..11c1dcdf --- /dev/null +++ b/kexi/plugins/scripting/scripts/exportxhtml/ExportXHTML.rc @@ -0,0 +1,8 @@ +<KrossScripting> + <ScriptAction + name="exportxhtml" + text="Export Data to XHTML File" + icon="fileexport" + interpreter="python" + file="ExportXHTML.py" /> +</KrossScripting> diff --git a/kexi/plugins/scripting/scripts/exportxhtml/Makefile.am b/kexi/plugins/scripting/scripts/exportxhtml/Makefile.am new file mode 100644 index 00000000..1c7b9ca6 --- /dev/null +++ b/kexi/plugins/scripting/scripts/exportxhtml/Makefile.am @@ -0,0 +1,4 @@ +include $(top_srcdir)/kexi/Makefile.global + +scriptsdir = $(kde_datadir)/kexi/scripts/exportxhtml +scripts_SCRIPTS = ExportXHTML.py ExportXHTML.rc diff --git a/kexi/plugins/scripting/scripts/importxhtml/ImportXHTML.py b/kexi/plugins/scripting/scripts/importxhtml/ImportXHTML.py new file mode 100755 index 00000000..200b3dee --- /dev/null +++ b/kexi/plugins/scripting/scripts/importxhtml/ImportXHTML.py @@ -0,0 +1,434 @@ +""" +Import data from a XHTML file to a KexiDB table. + +Description: +This script implements import of data from a XHTML file to a KexiDB table. The +table needs to be an already existing table the data should be added to. + +Author: +Sebastian Sauer <mail@dipe.org> + +Copyright: +Dual-licensed under LGPL v2+higher and the BSD license. +""" + +class SaxInput: + """ The inputsource we like to import the data from. This class + provides us abstract access to the SAX XML parser we use internaly + to import data from the XML-file. """ + + xmlfile = None + """ The XML file we should read the content from. """ + + def __init__(self): + """ Constructor. """ + + # try to import the xml.sax python module. + try: + import xml.sax.saxlib + import xml.sax.saxexts + except: + raise "Import of the python xml.sax.saxlib module failed. This module is needed by the ImportXHTML python script." + + def read(self, outputwriter): + """ Start reading and parsing the XML-file. """ + + import xml.sax.saxlib + import xml.sax.saxexts + + class SaxHandler(xml.sax.saxlib.HandlerBase): + """ The SaxHandler is our event-handler SAX calls on + parsing the XML-file. """ + + tablebase = ["html","body","table"] + """ The table-base defines where we will find our table-tag + that holds all the data we are interessted at. The default + is to look at <html><body><table></table></body></html>. """ + + def __init__(self, inputreader, outputwriter): + """ Constructor. """ + + # The to a SaxInput instance pointing inputreader. + self.inputreader = inputreader + # The to a KexiDBOutput instance pointing outputwriter. + self.outputwriter = outputwriter + # The hierachy-level in the DOM-tree we are in. + self.level = 0 + # Defines if we are in the with tablebase defined DOM-element. + self.intable = False + + # Points to a KexiDBOutput.Record instance if we are in a DOM-element that defines a record. + self.record = None + # Points to a KexiDBOutput.Field instance if we are in a record's field. + self.field = None + + def startDocument(self): + sys.stdout.write('=> Starting parsing\n') + + def endDocument(self): + sys.stdout.write('=> Fineshed parsing\n') + + def startElement(self, name, attrs): + """ This method is called by SAX if a DOM-element starts. """ + + if self.level < len(self.tablebase): + if self.tablebase[self.level] != name: + self.intable = False + else: + self.intable = True + self.level += 1 + if not self.intable: + return + + # Print some debugging-output to stdout. + for idx in range(self.level): sys.stdout.write(' ') + sys.stdout.write('Element: %s' % name) + for attrName in attrs.keys(): + sys.stdout.write(' %s="%s"' % (attrName,attrs.get(attrName))) + sys.stdout.write('\n') + + # handle tr-, th- and td-tags inside the table. + if name == "tr" and (self.level == len(self.tablebase) + 1): + self.record = self.outputwriter.Record() + elif name == "td" and (self.level == len(self.tablebase) + 2): + self.field = self.outputwriter.Field() + elif name == "th" and (self.level == len(self.tablebase) + 2): + self.field = self.outputwriter.Field() + + def endElement(self, name): + """ This method is called by SAX if a DOM-Element ends. """ + + self.level -= 1 + #sys.stdout.write('EndElement:%s level:%s len(self.tablebase):%s\n' % (name,self.level,len(self.tablebase))) + + if self.record != None: + # a record is defined. so, we are looking for the matching + # end-tags to close a record or a field. + if name == "tr" and (self.level == len(self.tablebase)): + self.outputwriter.write(self.record) + self.record = None + self.field = None + elif name == "td" and (self.level == len(self.tablebase) + 1): + #if self.field == None: + # raise "Unexpected closing </td>" + self.record.setField( self.field ) + self.field = None + elif name == "th" and (self.level == len(self.tablebase) + 1): + #if self.field == None: + # raise "Unexpected closing </td>" + self.record.setHeader( self.field ) + self.field = None + + def characters(self, chars, offset, length): + """ This method is called by SAX if the text-content of a DOM-Element + was parsed. """ + + if self.field != None: + # the xml-data is unicode and we need to encode it + # to latin-1 cause KexiDB deals only with latin-1. + u = unicode(chars[offset:offset+length]) + self.field.append(u.encode("latin-1")) + + # start the job + outputwriter.begin() + # create saxhandler to handle parsing events. + handler = SaxHandler(self, outputwriter) + # we need a sax-parser and connect it with the handler. + parser = xml.sax.saxexts.make_parser() + parser.setDocumentHandler(handler) + # open the XML-file, parse the content and close the file again. + f = file(self.xmlfile, 'r') + parser.parseFile(f) + f.close() + # job is done + outputwriter.end() + +class KexiDBOutput: + """ The destination target we like to import the data to. This class + provides abstract access to the KexiDB module. """ + + class Result: + """ Holds some informations about the import-result. """ + def __init__(self, outputwriter): + self.outputwriter = outputwriter + # number of records successfully imported. + self.successcount = 0 + # number of records where import failed. + self.failedcount = 0 + + def addLog(self, record, state): + import datetime + date = datetime.datetime.now().strftime("%Y-%m-%d %H:%M.%S") + self.outputwriter.logfile.write("%s (%s) %s\n" % (date,state,str(record))) + + def success(self, record): + """ Called if a record was written successfully. """ + print "SUCCESS: %s" % str(record) + self.successcount += 1 + if hasattr(self.outputwriter,"logfile"): + self.addLog(record, "Success") + + def failed(self, record): + """ Called if we failed to write a record. """ + print "FAILED: %s" % str(record) + self.failedcount += 1 + if hasattr(self.outputwriter,"logfile"): + self.addLog(record, "Failed") + + class Record: + """ A Record in the dataset. """ + def __init__(self): + self.fields = [] + def setHeader(self, headerfield): + self.fields.append( headerfield ) + self.isHeader = True + def setField(self, field): + self.fields.append( field ) + def __str__(self): + s = "[" + for f in self.fields: + s += "%s, " % str(f) + return s + "]" + + class Field: + """ A field in a record. """ + def __init__(self): + self.content = [] + def append(self, content): + self.content.append( content ) + def __str__(self): + return "".join(self.content) + + def __init__(self): + """ Constructor. """ + import kexiapp + keximainwindow = kexiapp.get("KexiAppMainWindow") + + try: + self.connection = keximainwindow.getConnection() + except: + raise "No connection established. Please open a project before." + + self.fieldlist = None + self.headerrecord = None + self.mapping = {} + + def begin(self): + """ Called before parsing starts. """ + print "START JOB" + if self.fieldlist == None: + raise "Invalid tableschema or fieldlist!" + global KexiDBOutput + self.result = KexiDBOutput.Result(self) + if hasattr(self,"logfilename") and self.logfilename != None and self.logfilename != "": + self.logfile = open(self.logfilename,'w') + + def end(self): + """ Called if parsing is fineshed. """ + print "END JOB" + self.logfile = None + self.mapping = {} + #self.headerrecord = None + + def getTables(self): + """ return a list of avaiable tablenames. """ + tables = self.connection.tableNames() + tables.sort() + return tables + + def setTable(self, tablename): + """ Set the tablename we like to import the data to. """ + tableschema = self.connection.tableSchema(tablename) + if tableschema == None: + raise "There exists no table with the name '%s'!" % tablename + self.fieldlist = tableschema.fieldlist() + fields = self.fieldlist.fields() + for field in fields: + print "KexiDBOutput.setTable(%s): %s(%s)" % (tablename,field.name(),field.type()) + print "names=%s" % self.fieldlist.names() + + def setMapping(self, mapping): + """ Set the tablefieldname=xmlcolnr dictonary we should map the data to. """ + self.mapping = mapping + + def setLogFile(self, logfilename): + """ Set the name of the logfile. """ + self.logfilename = logfilename + + def write(self, record): + """ Write the record to the KexiDB table. """ + + if hasattr(record, "isHeader"): + self.headerrecord = record + return + + sys.stdout.write('KexiDBOutput.write:') + for f in record.fields: + sys.stdout.write(' "%s"' % f) + sys.stdout.write('\n') + + if hasattr(self,"onWrite"): + if not self.onWrite(record): + raise RuntimeError() + delattr(self,"onWrite") + self.fieldlist = self.fieldlist.subList( list( self.mapping ) ) + + # Translate a KexiDBOutput.Record into a list of values. + values = [] + for k in self.fieldlist.names(): + values.append( str(record.fields[ int(self.mapping[k]) ]) ) + print "Import values: %s" % values + + try: + if self.connection.insertRecord(self.fieldlist, values): + self.result.success(record) + else: + self.result.failed(record) + except: + err = self.connection.lastError() + raise Exception( "Failed to insert the record:\n%s\n\n%s" % (values,err) ) + #raise Exception( "Failed to insert into table \"%s\" the record:\n%s\n%s" % (self.tableschema.name(),values,self.connection.lastError()) ) + +class GuiApp: + """ The GUI-dialog displayed to let the user define the source + XML-file and the destination KexiDB table. """ + + class InitialDialog: + def __init__(self, guiapp): + self.guiapp = guiapp + self.ok = False + + import gui + self.dialog = gui.Dialog("Import XHTML") + self.dialog.addLabel(self.dialog, "Import data from a XHTML-file to a KexiDB table.\n" + "The destination table needs to be an existing table the data should be added to.") + self.importfile = self.dialog.addFileChooser(self.dialog, + "Source File:", + gui.getHome() + "/kexidata.xhtml", + (('XHTML files', '*.xhtml'),('All files', '*'))) + + self.desttable = self.dialog.addList(self.dialog, "Destination Table:", self.guiapp.outputwriter.getTables()) + + #self.operation = self.dialog.addList(self.dialog, "Operation:", ("Insert","Update","Insert/Update")) + #self.error = self.dialog.addList(self.dialog, "On error:", ("Ask","Skip","Abort")) + + self.logfile = self.dialog.addFileChooser(self.dialog, + "Log File:", + "", + (('Logfiles', '*.log'),('All files', '*'))) + + btnframe = self.dialog.addFrame(self.dialog) + self.dialog.addButton(btnframe, "Next", self.doNext) + self.dialog.addButton(btnframe, "Cancel", self.doCancel) + self.dialog.show() + + def doCancel(self): + """ Called if the Cancel-button was pressed. """ + self.dialog.close() + self.dialog = None + #self.guiapp.InitialDialog + + def doNext(self): + """ Start to import the XML-file into the KexiDB table. """ + + self.guiapp.inputreader.xmlfile = str(self.importfile.get()) + self.guiapp.outputwriter.setTable( str(self.desttable.get()) ) + self.guiapp.outputwriter.setLogFile( str(self.logfile.get()) ) + + try: + self.guiapp.inputreader.read( self.guiapp.outputwriter ) + + msgbox = self.dialog.showMessageBox("info","Import done", + "Successfully imported records: %s\nFailed to import records: %s" % (self.guiapp.outputwriter.result.successcount, self.guiapp.outputwriter.result.failedcount) ) + msgbox.show() + + self.doCancel() + except RuntimeError, e: + pass + #except Exception, e: + # import traceback + # traceback.print_exc() + # msgbox = self.dialog.showMessageBox("error", "Error", e) + # msgbox.show() + + class MapperDialog: + """ The dialog that provides us a way to map + XHTML columns to the destination table. """ + + def __init__(self, outputwriter, record): + self.outputwriter = outputwriter + self.ok = False + fieldlist = outputwriter.fieldlist + + import gui + self.dlg = gui.Dialog("Import XHTML") + self.dlg.addLabel(self.dlg, "Define how the destination table should be mapped to the data from the XHTML file.") + values = ["",] + for i in range(len(record.fields)): + try: + values.append( "%s: %s" % (i,str(outputwriter.headerrecord.fields[i])) ) + except: + values.append( "%s: (%s)" % (i,str(record.fields[i])) ) + + self.items = [] + i = 0 + for field in fieldlist.fields(): + f = self.dlg.addFrame(self.dlg) + + l = self.dlg.addList(f, "%s:" % field.name(), values) + self.items.append( (field,l) ) + + details = "%s:" % str( field.type() ) + if field.isAutoInc(): details += "autoinc," + if field.isUniqueKey(): details += "unique," + if field.isNotNull(): details += "notnull," + if field.isNotEmpty(): details += "notempty," + self.dlg.addLabel(f, "(%s)" % details[:-1]) + + try: + variable = str( record.fields[i] ) + try: + int(variable) + i += 1 + if not field.isAutoInc(): + l.set(i) + except ValueError, e: + if not field.type() in ("Integer","BigInteger","ShortInteger","Float","Double"): + i += 1 + l.set(i) + except: + pass + + btnframe = self.dlg.addFrame(self.dlg) + self.dlg.addButton(btnframe, "Next", self.doNext) + self.dlg.addButton(btnframe, "Cancel", self.dlg.close) + self.dlg.show() + + def doNext(self): + mapping = {} + for item in self.items: + (field,l) = item + fieldname = field.name() + colnr = str( l.get() ).split(":",1)[0] + if colnr.isdigit(): + print "Table field '%s' is mapped to XML column '%s'" % (fieldname,colnr) + mapping[ fieldname ] = colnr + self.outputwriter.setMapping(mapping) + self.ok = True + self.dlg.close() + + def __init__(self, inputreader, outputwriter): + """ Constructor. """ + + self.inputreader = inputreader + self.outputwriter = outputwriter + self.outputwriter.onWrite = self.onWrite + + self.InitialDialog(self) + + def onWrite(self, record): + """ This method got called after the first record got + readed and before we start to import. """ + return self.MapperDialog(self.outputwriter, record).ok + +GuiApp( SaxInput(), KexiDBOutput() ) diff --git a/kexi/plugins/scripting/scripts/importxhtml/ImportXHTML.rc b/kexi/plugins/scripting/scripts/importxhtml/ImportXHTML.rc new file mode 100644 index 00000000..0cfe7718 --- /dev/null +++ b/kexi/plugins/scripting/scripts/importxhtml/ImportXHTML.rc @@ -0,0 +1,8 @@ +<KrossScripting> + <ScriptAction + name="importxhtml" + text="Import Data From XHTML File" + icon="fileimport" + interpreter="python" + file="ImportXHTML.py" /> +</KrossScripting> diff --git a/kexi/plugins/scripting/scripts/importxhtml/Makefile.am b/kexi/plugins/scripting/scripts/importxhtml/Makefile.am new file mode 100644 index 00000000..a0a424fa --- /dev/null +++ b/kexi/plugins/scripting/scripts/importxhtml/Makefile.am @@ -0,0 +1,4 @@ +include $(top_srcdir)/kexi/Makefile.global + +scriptsdir = $(kde_datadir)/kexi/scripts/importxhtml +scripts_SCRIPTS = ImportXHTML.py ImportXHTML.rc diff --git a/kexi/plugins/scripting/scripts/projectdocumentor/Makefile.am b/kexi/plugins/scripting/scripts/projectdocumentor/Makefile.am new file mode 100644 index 00000000..9d32e165 --- /dev/null +++ b/kexi/plugins/scripting/scripts/projectdocumentor/Makefile.am @@ -0,0 +1,4 @@ +include $(top_srcdir)/kexi/Makefile.global + +scriptsdir = $(kde_datadir)/kexi/scripts/projectdocumentor +scripts_SCRIPTS = ProjectDocumentor.py ProjectDocumentor.rc diff --git a/kexi/plugins/scripting/scripts/projectdocumentor/ProjectDocumentor.py b/kexi/plugins/scripting/scripts/projectdocumentor/ProjectDocumentor.py new file mode 100755 index 00000000..89a60301 --- /dev/null +++ b/kexi/plugins/scripting/scripts/projectdocumentor/ProjectDocumentor.py @@ -0,0 +1,186 @@ +""" +Project Documentor + +Description: +This script collects various informations about a Kexi project +and exports them to a HTML file. + +Author: +Sebastian Sauer <mail@dipe.org> + +Copyright: +Dual-licensed under LGPL v2+higher and the BSD license. +""" + +class DataProvider: + def __init__(self): + import kexiapp + keximainwindow = kexiapp.get("KexiAppMainWindow") + + try: + self.connection = keximainwindow.getConnection() + except: + raise "No connection established. Please open the project to be documented first." + + def printConnection(self): + condata = self.connection.data() + infos = [] + for item in ("caption", "description", "driverName", "hostName", "port", "userName", "fileName", "dbPath", "localSocketFileName", "serverInfoString"): + result = getattr(condata, item)() + if result != None and result != "" and (item != "port" or result != 0): + infos.append( (item, result) ) + return infos + + def printDriver(self): + driver = self.connection.driver() + result = [ ("Version", "%s.%s" % (driver.versionMajor(),driver.versionMinor())) ] + conlist = driver.connectionsList() + if len(conlist) > 0: + result.append( ("Connections",str(conlist)) ) + return result + + def printDatabases(self): + result = [ ("Current database", self.connection.currentDatabase()) ] + dbnames = self.connection.databaseNames() + if len(dbnames) > 0: + result.append( ("Databases",str(dbnames)) ) + return result + + def printTables(self): + result = [] + for t in self.connection.tableNames(): + tableschema = self.connection.tableSchema(t) + ti = [] + for i in ("name", "caption", "description"): + v = getattr(tableschema,i)() + if v != None and v != "": + ti.append( (i,v) ) + tf = [] + for field in tableschema.fieldlist().fields(): + tfi = [] + for n in ("caption","description","type","subType","typeGroup","length","defaultValue"): + v = getattr(field,n)() + if v != None and v != "": + tfi.append( (n,v) ) + props = [] + for n in ("PrimaryKey","ForeignKey","AutoInc","UniqueKey","NotNull", "NotEmpty","Indexed","Unsigned"): + v = getattr(field,"is%s" % n)() + if v != None and v != "" and v != False and v != 0: + props.append( "%s " % n ) + if len(props) > 0: + tfi.append( ("properties",props) ) + + tf.append( (field.name(), tfi) ) + ti.append( ("fields", tf) ) + if len(ti) > 0: + result.append( (t, ti) ) + return result + + def printQueries(self): + result = [] + for q in self.connection.queryNames(): + queryschema = self.connection.querySchema(q) + qi = [] + for i in ("name", "caption", "description", "statement"): + v = getattr(queryschema,i)() + if v != None and v != "": + qi.append( (i,v) ) + if len(qi) > 0: + result.append( (q, qi) ) + return result + +class GuiApp: + def __init__(self, dataprovider): + self.dataprovider = dataprovider + + try: + import gui + except: + raise "Import of the Kross GUI module failed." + + self.dialog = gui.Dialog("Project Documentor") + + self.dialog.addLabel(self.dialog, "Save information about the project to an HTML file.") + + self.file = self.dialog.addFileChooser(self.dialog, + "File:", + gui.getHome() + "/projectdoc.html", + (('HTML files', '*.html'),('All files', '*'))) + + self.printCheckBoxes = {} + for d in dir(self.dataprovider): + if d.startswith("print"): + self.printCheckBoxes[d] = self.dialog.addCheckBox(self.dialog, d[5:], True) + + #value = getattr(self.dataprovider,d)() + #if value != None and len(value) > 0: + # f.write("<h2>%s</h2>" % d[5:]) + # f.write( self.toHTML(value) ) + + #self.exportProjectdetails = + #self.exportTableschemas = self.dialog.addCheckBox(self.dialog, "Table schemas", True) + #self.exportQueryschemas = self.dialog.addCheckBox(self.dialog, "Query schemas", True) + + btnframe = self.dialog.addFrame(self.dialog) + self.dialog.addButton(btnframe, "Save", self.doSave) + self.dialog.addButton(btnframe, "Cancel", self.dialog.close) + + self.dialog.show() + + def toHTML(self, value): + import types + result = "" + if isinstance(value, types.TupleType): + result += "<ul>" + if len(value) == 1: + result += "<li>%s</li>" % value + elif len(value) == 2: + result += "<li>%s: %s</li>" % (value[0], self.toHTML(value[1])) + elif len(value) > 2: + for item in value: + i = self.toHTML(item) + if i != "": + result += "<li>%s</li>" % i + result += "</ul>" + elif isinstance(value, types.ListType): + for item in value: + result += "%s" % self.toHTML(item) + else: + result += "%s" % value + return result + + def doSave(self): + file = str( self.file.get() ) + print "Attempting to save project documentation to file: %s" % file + + f = open(file, "w") + + f.write("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>") + f.write("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 4.01 Strict//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11-strict.dtd\">") + f.write("<html><head><title>Project information</title>") + f.write("<style type=\"text/css\">") + f.write(" html { background-color:#fafafa; }") + f.write(" body { background-color:#ffffff; margin:1em; padding:1em; border:#99a 1px solid; color:#003; }") + f.write("</style>") + f.write("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />") + f.write("</head><body><h1>Project information</h1>") + + for d in dir(self.dataprovider): + if d.startswith("print"): + print "GuiApp.doSave() CHECK %s" % d + a = self.printCheckBoxes[d] + if a and a.isChecked(): + print "GuiApp.doSave() BEGIN %s" % d + value = getattr(self.dataprovider,d)() + if value != None and len(value) > 0: + f.write("<h2>%s</h2>" % d[5:]) + f.write( self.toHTML(value) ) + print "GuiApp.doSave() END %s" % d + + f.close() + + print "Successfully saved project documentation to file: %s" % file + self.dialog.close() + +GuiApp( DataProvider() ) + diff --git a/kexi/plugins/scripting/scripts/projectdocumentor/ProjectDocumentor.rc b/kexi/plugins/scripting/scripts/projectdocumentor/ProjectDocumentor.rc new file mode 100644 index 00000000..bb0f6c69 --- /dev/null +++ b/kexi/plugins/scripting/scripts/projectdocumentor/ProjectDocumentor.rc @@ -0,0 +1,8 @@ +<KrossScripting> + <ScriptAction + name="projectdocumentor" + text="Project Documentation Generator" + icon="contents" + interpreter="python" + file="ProjectDocumentor.py" /> +</KrossScripting> diff --git a/kexi/plugins/scripting/scripts/python/Makefile.am b/kexi/plugins/scripting/scripts/python/Makefile.am new file mode 100644 index 00000000..4b31c35a --- /dev/null +++ b/kexi/plugins/scripting/scripts/python/Makefile.am @@ -0,0 +1,2 @@ +include $(top_srcdir)/kexi/Makefile.global +SUBDIRS = kexiapp diff --git a/kexi/plugins/scripting/scripts/python/kexiapp/Makefile.am b/kexi/plugins/scripting/scripts/python/kexiapp/Makefile.am new file mode 100644 index 00000000..f0f0492d --- /dev/null +++ b/kexi/plugins/scripting/scripts/python/kexiapp/Makefile.am @@ -0,0 +1,2 @@ +kexiapppythondir = $(kde_datadir)/kexi/kross/python/kexiapp +kexiapppython_SCRIPTS = __init__.py diff --git a/kexi/plugins/scripting/scripts/python/kexiapp/__init__.py b/kexi/plugins/scripting/scripts/python/kexiapp/__init__.py new file mode 100755 index 00000000..b5224304 --- /dev/null +++ b/kexi/plugins/scripting/scripts/python/kexiapp/__init__.py @@ -0,0 +1,25 @@ +""" +Initializer for the krosskexiapp-module. + +Description: +This module provides the entry-point for python scripts +to work with a running Kexi application instance. + +Author: +Sebastian Sauer <mail@dipe.org> + +Copyright: +Dual-licensed under LGPL v2+higher and the BSD license. +""" + +try: + import krosskexiapp +except ImportError, e: + raise "Import of the Kross KexiApp module failed.\n%s" % e + +def get(modulename): + return krosskexiapp.get(modulename) + +def currentConnection(): + mainwindow = krosskexiapp.get("KexiAppMainWindow") + return mainwindow.getConnection() diff --git a/kexi/plugins/tables/Makefile.am b/kexi/plugins/tables/Makefile.am new file mode 100644 index 00000000..0971a64e --- /dev/null +++ b/kexi/plugins/tables/Makefile.am @@ -0,0 +1,28 @@ +include $(top_srcdir)/kexi/Makefile.global + +kde_module_LTLIBRARIES = kexihandler_table.la + +kexihandler_table_la_SOURCES = kexitablepart.cpp kexitabledesignerview.cpp kexitabledesignerview_p.cpp \ + kexitabledesigner_dataview.cpp kexitabledesignercommands.cpp kexilookupcolumnpage.cpp + +kexihandler_table_la_LDFLAGS = $(all_libraries) $(KDE_PLUGIN) $(VER_INFO) -module +kexihandler_table_la_LIBADD = $(top_builddir)/kexi/core/libkexicore.la \ + $(top_builddir)/kexi/kexidb/libkexidb.la \ + $(top_builddir)/kexi/widget/tableview/libkexidatatable.la \ + $(top_builddir)/kexi/widget/libkexiextendedwidgets.la \ + $(top_builddir)/lib/koproperty/libkoproperty.la + +INCLUDES= $(KOFFICE_INCLUDES) \ + -I$(top_srcdir)/kexi/core -I$(top_srcdir)/kexi \ + -I$(top_srcdir)/kexi/widget -I$(top_srcdir)/kexi/widget/tableview \ + -I$(top_srcdir)/kexi/kexidb -I$(top_srcdir)/lib $(all_includes) + +servicesdir=$(kde_servicesdir)/kexi +services_DATA=kexitablehandler.desktop + +rcdir = $(kde_datadir)/kexi +rc_DATA = kexitablepartui.rc kexitablepartinstui.rc + +METASOURCES = AUTO + +include ../Makefile.common diff --git a/kexi/plugins/tables/kexilookupcolumnpage.cpp b/kexi/plugins/tables/kexilookupcolumnpage.cpp new file mode 100644 index 00000000..9df92794 --- /dev/null +++ b/kexi/plugins/tables/kexilookupcolumnpage.cpp @@ -0,0 +1,419 @@ +/* This file is part of the KDE project + Copyright (C) 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 "kexilookupcolumnpage.h" + +#include <qlabel.h> +#include <qlayout.h> +#include <qtooltip.h> +#include <qheader.h> + +#include <kiconloader.h> +#include <klocale.h> +#include <ktoolbarbutton.h> +#include <kdebug.h> +#include <kpopupmenu.h> + +#include <widget/kexipropertyeditorview.h> +#include <widget/kexidatasourcecombobox.h> +#include <widget/kexifieldlistview.h> +#include <widget/kexifieldcombobox.h> +#include <widget/kexismalltoolbutton.h> +#include <kexidb/connection.h> +#include <kexiproject.h> + +#include <koproperty/property.h> +#include <koproperty/utils.h> + +QString mimeTypeToType(const QString& mimeType) +{ + if (mimeType=="kexi/table") + return "table"; + else if (mimeType=="kexi/query") + return "query"; +//! @todo more types + return mimeType; +} + +QString typeToMimeType(const QString& type) +{ + if (type=="table") + return "kexi/table"; + else if (type=="query") + return "kexi/query"; +//! @todo more types + return type; +} + +//---------------------------------------------- + +//! @internal +class KexiLookupColumnPage::Private +{ + public: + Private() + : currentFieldUid(-1) + , insideClearRowSourceSelection(false) + , propertySetEnabled(true) + { + } + ~Private() + { + } + + bool hasPropertySet() const { + return propertySet; + } + + void setPropertySet(KoProperty::Set* aPropertySet) { + propertySet = aPropertySet; + } + + QVariant propertyValue(const QCString& propertyName) const { + return propertySet ? propertySet->property(propertyName).value() : QVariant(); + } + + void changeProperty(const QCString &property, const QVariant &value) + { + if (!propertySetEnabled) + return; + propertySet->changeProperty(property, value); + } + + void updateInfoLabelForPropertySet(const QString& textToDisplayForNullSet) { + KexiPropertyEditorView::updateInfoLabelForPropertySet( + objectInfoLabel, propertySet, textToDisplayForNullSet); + } + + KexiDataSourceComboBox *rowSourceCombo; + KexiFieldComboBox *boundColumnCombo, *visibleColumnCombo; + KexiObjectInfoLabel *objectInfoLabel; + QLabel *rowSourceLabel, *boundColumnLabel, *visibleColumnLabel; + QToolButton *clearRowSourceButton, *gotoRowSourceButton, *clearBoundColumnButton, + *clearVisibleColumnButton; + //! Used only in assignPropertySet() to check whether we already have the set assigned + int currentFieldUid; + + bool insideClearRowSourceSelection : 1; + //! True is changeProperty() works. Used to block updating properties when within assignPropertySet(). + bool propertySetEnabled : 1; + + private: + //! A property set that is displayed on the page. + //! The set is also updated after any change in this page's data. + QGuardedPtr<KoProperty::Set> propertySet; +}; + +//---------------------------------------------- + +KexiLookupColumnPage::KexiLookupColumnPage(QWidget *parent) + : QWidget(parent) + , d(new Private()) +{ + setName("KexiLookupColumnPage"); + + QVBoxLayout *vlyr = new QVBoxLayout(this); + d->objectInfoLabel = new KexiObjectInfoLabel(this, "KexiObjectInfoLabel"); + vlyr->addWidget(d->objectInfoLabel); + +//todo d->noDataSourceAvailableSingleText = i18n("No data source could be assigned for this widget."); +//todo d->noDataSourceAvailableMultiText = i18n("No data source could be assigned for multiple widgets."); + + //-Row Source + QWidget *contents = new QWidget(this); + vlyr->addWidget(contents); + QVBoxLayout *contentsVlyr = new QVBoxLayout(contents); + + QHBoxLayout *hlyr = new QHBoxLayout(contentsVlyr); + d->rowSourceLabel = new QLabel(i18n("Row source:"), contents); + d->rowSourceLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); + d->rowSourceLabel->setMargin(2); + d->rowSourceLabel->setMinimumHeight(IconSize(KIcon::Small)+4); + d->rowSourceLabel->setAlignment(Qt::AlignLeft|Qt::AlignBottom); + hlyr->addWidget(d->rowSourceLabel); + + d->gotoRowSourceButton = new KexiSmallToolButton(contents, QString::null, "goto", "gotoRowSourceButton"); + d->gotoRowSourceButton->setMinimumHeight(d->rowSourceLabel->minimumHeight()); + QToolTip::add(d->gotoRowSourceButton, i18n("Go to selected row source")); + hlyr->addWidget(d->gotoRowSourceButton); + connect(d->gotoRowSourceButton, SIGNAL(clicked()), this, SLOT(slotGotoSelectedRowSource())); + + d->clearRowSourceButton = new KexiSmallToolButton(contents, QString::null, + "clear_left", "clearRowSourceButton"); + d->clearRowSourceButton->setMinimumHeight(d->rowSourceLabel->minimumHeight()); + QToolTip::add(d->clearRowSourceButton, i18n("Clear row source")); + hlyr->addWidget(d->clearRowSourceButton); + connect(d->clearRowSourceButton, SIGNAL(clicked()), this, SLOT(clearRowSourceSelection())); + + d->rowSourceCombo = new KexiDataSourceComboBox(contents, "rowSourceCombo"); + d->rowSourceLabel->setBuddy(d->rowSourceCombo); + contentsVlyr->addWidget(d->rowSourceCombo); + + contentsVlyr->addSpacing(8); + + //- Bound Column + hlyr = new QHBoxLayout(contentsVlyr); + d->boundColumnLabel = new QLabel(i18n("Bound column:"), contents); + d->boundColumnLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); + d->boundColumnLabel->setMargin(2); + d->boundColumnLabel->setMinimumHeight(IconSize(KIcon::Small)+4); + d->boundColumnLabel->setAlignment(Qt::AlignLeft|Qt::AlignBottom); + hlyr->addWidget(d->boundColumnLabel); + + d->clearBoundColumnButton = new KexiSmallToolButton(contents, QString::null, + "clear_left", "clearBoundColumnButton"); + d->clearBoundColumnButton->setMinimumHeight(d->boundColumnLabel->minimumHeight()); + QToolTip::add(d->clearBoundColumnButton, i18n("Clear bound column")); + hlyr->addWidget(d->clearBoundColumnButton); + connect(d->clearBoundColumnButton, SIGNAL(clicked()), this, SLOT(clearBoundColumnSelection())); + + d->boundColumnCombo = new KexiFieldComboBox(contents, "boundColumnCombo"); + d->boundColumnLabel->setBuddy(d->boundColumnCombo); + contentsVlyr->addWidget(d->boundColumnCombo); + + contentsVlyr->addSpacing(8); + + //- Visible Column + hlyr = new QHBoxLayout(contentsVlyr); + d->visibleColumnLabel = new QLabel(i18n("Visible column:"), contents); + d->visibleColumnLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); + d->visibleColumnLabel->setMargin(2); + d->visibleColumnLabel->setMinimumHeight(IconSize(KIcon::Small)+4); + d->visibleColumnLabel->setAlignment(Qt::AlignLeft|Qt::AlignBottom); + hlyr->addWidget(d->visibleColumnLabel); + + d->clearVisibleColumnButton = new KexiSmallToolButton(contents, QString::null, + "clear_left", "clearVisibleColumnButton"); + d->clearVisibleColumnButton->setMinimumHeight(d->visibleColumnLabel->minimumHeight()); + QToolTip::add(d->clearVisibleColumnButton, i18n("Clear visible column")); + hlyr->addWidget(d->clearVisibleColumnButton); + connect(d->clearVisibleColumnButton, SIGNAL(clicked()), this, SLOT(clearVisibleColumnSelection())); + + d->visibleColumnCombo = new KexiFieldComboBox(contents, "visibleColumnCombo"); + d->visibleColumnLabel->setBuddy(d->visibleColumnCombo); + contentsVlyr->addWidget(d->visibleColumnCombo); + + vlyr->addStretch(1); + + connect(d->rowSourceCombo, SIGNAL(textChanged(const QString &)), + this, SLOT(slotRowSourceTextChanged(const QString &))); + connect(d->rowSourceCombo, SIGNAL(dataSourceChanged()), this, SLOT(slotRowSourceChanged())); + connect(d->boundColumnCombo, SIGNAL(selected()), this, SLOT(slotBoundColumnSelected())); + connect(d->visibleColumnCombo, SIGNAL(selected()), this, SLOT(slotVisibleColumnSelected())); + + clearBoundColumnSelection(); + clearVisibleColumnSelection(); +} + +KexiLookupColumnPage::~KexiLookupColumnPage() +{ + delete d; +} + +void KexiLookupColumnPage::setProject(KexiProject *prj) +{ + d->rowSourceCombo->setProject(prj, + true/*showTables*/, true/*showQueries*/ + ); + d->boundColumnCombo->setProject(prj); + d->visibleColumnCombo->setProject(prj); +} + +void KexiLookupColumnPage::assignPropertySet(KoProperty::Set* propertySet) +{ + if (!d->hasPropertySet() && !propertySet) + return; + if (propertySet && d->currentFieldUid == (*propertySet)["uid"].value().toInt()) + return; //already assigned + + d->propertySetEnabled = false; + d->setPropertySet( propertySet ); + d->updateInfoLabelForPropertySet( i18n("No field selected") ); + + const bool hasRowSource = d->hasPropertySet() && !d->propertyValue("rowSourceType").isNull() + && !d->propertyValue("rowSource").isNull(); + + QString rowSource, rowSourceType; + if (hasRowSource) { + rowSourceType = typeToMimeType( d->propertyValue("rowSourceType").toString() ); + rowSource = d->propertyValue("rowSource").toString(); + } + d->rowSourceCombo->setDataSource( rowSourceType, rowSource ); + d->rowSourceLabel->setEnabled( d->hasPropertySet() ); + d->rowSourceCombo->setEnabled( d->hasPropertySet() ); + if (!d->hasPropertySet()) + d->clearRowSourceButton->setEnabled( false ); + + int boundColumn = -1, visibleColumn = -1; + if (d->rowSourceCombo->isSelectionValid()) { + boundColumn = d->propertyValue("boundColumn").toInt(); + visibleColumn = d->propertyValue("visibleColumn").toInt(); + } + d->boundColumnCombo->setFieldOrExpression(boundColumn); + d->visibleColumnCombo->setFieldOrExpression(visibleColumn); + updateBoundColumnWidgetsAvailability(); + d->propertySetEnabled = true; +} + +void KexiLookupColumnPage::clearBoundColumnSelection() +{ + d->boundColumnCombo->setCurrentText(""); + d->boundColumnCombo->setFieldOrExpression(QString::null); + slotBoundColumnSelected(); + d->clearBoundColumnButton->setEnabled(false); +} + +void KexiLookupColumnPage::slotBoundColumnSelected() +{ +// KexiDB::Field::Type dataType = KexiDB::Field::InvalidType; +//! @todo this should also work for expressions +/*disabled KexiDB::Field *field = d->fieldListView->schema()->field( d->boundColumnCombo->fieldOrExpression() ); + if (field) + dataType = field->type(); +*/ + d->clearBoundColumnButton->setEnabled( !d->boundColumnCombo->fieldOrExpression().isEmpty() ); + if (!d->boundColumnCombo->fieldOrExpression().isEmpty()) { + kdDebug() << endl; + } + + // update property set + if (d->hasPropertySet()) { + d->changeProperty("boundColumn", d->boundColumnCombo->indexOfField()); + } +/* + emit boundColumnChanged( + d->boundColumnCombo->fieldOrExpression(), + d->boundColumnCombo->fieldOrExpressionCaption(), + dataType + );*/ +} + +void KexiLookupColumnPage::clearVisibleColumnSelection() +{ + d->visibleColumnCombo->setCurrentText(""); + d->visibleColumnCombo->setFieldOrExpression(QString::null); + slotVisibleColumnSelected(); + d->clearVisibleColumnButton->setEnabled(false); +} + +void KexiLookupColumnPage::slotVisibleColumnSelected() +{ +// KexiDB::Field::Type dataType = KexiDB::Field::InvalidType; +//! @todo this should also work for expressions + d->clearVisibleColumnButton->setEnabled( !d->visibleColumnCombo->fieldOrExpression().isEmpty() ); + + // update property set + if (d->hasPropertySet()) { +//! @todo support expression in special "visibleExpression" + d->changeProperty("visibleColumn", d->visibleColumnCombo->indexOfField()); + } +} + +void KexiLookupColumnPage::slotRowSourceChanged() +{ + if (!d->rowSourceCombo->project()) + return; + QString mime = d->rowSourceCombo->selectedMimeType(); + bool rowSourceFound = false; + QString name = d->rowSourceCombo->selectedName(); + if ((mime=="kexi/table" || mime=="kexi/query") && d->rowSourceCombo->isSelectionValid()) { + KexiDB::TableOrQuerySchema *tableOrQuery = new KexiDB::TableOrQuerySchema( + d->rowSourceCombo->project()->dbConnection(), name.latin1(), mime=="kexi/table"); + if (tableOrQuery->table() || tableOrQuery->query()) { +//disabled d->fieldListView->setSchema( tableOrQuery ); +/*tmp*/ delete tableOrQuery; + rowSourceFound = true; + d->boundColumnCombo->setTableOrQuery(name, mime=="kexi/table"); + d->visibleColumnCombo->setTableOrQuery(name, mime=="kexi/table"); + } + else { + delete tableOrQuery; + } + } + if (!rowSourceFound) { + d->boundColumnCombo->setTableOrQuery("", true); + d->visibleColumnCombo->setTableOrQuery("", true); + } + clearBoundColumnSelection(); + clearVisibleColumnSelection(); + d->clearRowSourceButton->setEnabled(rowSourceFound); + d->gotoRowSourceButton->setEnabled(rowSourceFound); +/* disabled + if (dataSourceFound) { + slotFieldListViewSelectionChanged(); + } else { + d->addField->setEnabled(false); + }*/ + updateBoundColumnWidgetsAvailability(); + + //update property set + if (d->hasPropertySet()) { + d->changeProperty("rowSourceType", mimeTypeToType(mime)); + d->changeProperty("rowSource", name); + } + +//disabled emit formDataSourceChanged(mime, name); +//! @todo update d->propertySet ^^ +} + +void KexiLookupColumnPage::slotRowSourceTextChanged(const QString & string) +{ + Q_UNUSED(string); + const bool enable = d->rowSourceCombo->isSelectionValid(); + if (enable) { + updateBoundColumnWidgetsAvailability(); + } + else { + clearRowSourceSelection( d->rowSourceCombo->selectedName().isEmpty()/*alsoClearComboBox*/ ); + } +} + +void KexiLookupColumnPage::clearRowSourceSelection(bool alsoClearComboBox) +{ + if (d->insideClearRowSourceSelection) + return; + d->insideClearRowSourceSelection = true; + if (alsoClearComboBox && !d->rowSourceCombo->selectedName().isEmpty()) + d->rowSourceCombo->setDataSource("", ""); + d->clearRowSourceButton->setEnabled(false); + d->gotoRowSourceButton->setEnabled(false); + d->insideClearRowSourceSelection = false; +} + +void KexiLookupColumnPage::slotGotoSelectedRowSource() +{ + QString mime = d->rowSourceCombo->selectedMimeType(); + if (mime=="kexi/table" || mime=="kexi/query") { + if (d->rowSourceCombo->isSelectionValid()) + emit jumpToObjectRequested(mime.latin1(), d->rowSourceCombo->selectedName().latin1()); + } +} + +void KexiLookupColumnPage::updateBoundColumnWidgetsAvailability() +{ + const bool hasRowSource = d->rowSourceCombo->isSelectionValid(); + d->boundColumnCombo->setEnabled( hasRowSource ); + d->boundColumnLabel->setEnabled( hasRowSource ); + d->clearBoundColumnButton->setEnabled( hasRowSource && !d->boundColumnCombo->fieldOrExpression().isEmpty() ); + d->visibleColumnCombo->setEnabled( hasRowSource ); + d->visibleColumnLabel->setEnabled( hasRowSource ); + d->clearVisibleColumnButton->setEnabled( hasRowSource && !d->visibleColumnCombo->fieldOrExpression().isEmpty() ); +} + +#include "kexilookupcolumnpage.moc" diff --git a/kexi/plugins/tables/kexilookupcolumnpage.h b/kexi/plugins/tables/kexilookupcolumnpage.h new file mode 100644 index 00000000..457b2e3d --- /dev/null +++ b/kexi/plugins/tables/kexilookupcolumnpage.h @@ -0,0 +1,88 @@ +/* This file is part of the KDE project + Copyright (C) 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 KEXILOOKUPCOLUMNPAGE_H +#define KEXILOOKUPCOLUMNPAGE_H + +#include <qwidget.h> +#include <kexidb/field.h> +#include <kexidb/utils.h> +#include <koproperty/set.h> + +class KCommand; +class KexiObjectInfoLabel; +class KexiDataSourceComboBox; +class KexiFieldComboBox; +class KexiFieldListView; +class KexiProject; +class KexiSmallToolButton; +class QToolButton; +class QLabel; +class QFrame; + +//! @short A page within table designer's property pane, providing lookup column editor. +/*! It's data model is basically KexiDB::LookupFieldSchema class, but the page does + not create it directly but instead updates a property set that defines + the field currently selected in the designer. + + @todo not all features of KexiDB::LookupFieldSchema class are displayed on this page yet + */ +class KexiLookupColumnPage : public QWidget +{ + Q_OBJECT + + public: + KexiLookupColumnPage(QWidget *parent); + virtual ~KexiLookupColumnPage(); + + public slots: + void setProject(KexiProject *prj); + void clearRowSourceSelection(bool alsoClearComboBox = true); + void clearBoundColumnSelection(); + void clearVisibleColumnSelection(); + + //! Receives a pointer to a new property \a set (from KexiFormView::managerPropertyChanged()) + void assignPropertySet(KoProperty::Set* propertySet); + + signals: + //! Signal emitted when helper button 'Go to selected row sourcesource' is clicked. + void jumpToObjectRequested(const QCString& mime, const QCString& name); + +// /*! Signal emitted when current bound column has been changed. */ +// void boundColumnChanged(const QString& string, const QString& caption, + // KexiDB::Field::Type type); + + protected slots: + void slotRowSourceTextChanged(const QString & string); + void slotRowSourceChanged(); + void slotGotoSelectedRowSource(); + void slotBoundColumnSelected(); + void slotVisibleColumnSelected(); + + protected: + void updateBoundColumnWidgetsAvailability(); + + //! Used instead of m_propertySet->changeProperty() to honor m_propertySetEnabled + void changeProperty(const QCString &property, const QVariant &value); + + private: + class Private; + Private* d; +}; + +#endif diff --git a/kexi/plugins/tables/kexitabledesigner_dataview.cpp b/kexi/plugins/tables/kexitabledesigner_dataview.cpp new file mode 100644 index 00000000..bea2d9f5 --- /dev/null +++ b/kexi/plugins/tables/kexitabledesigner_dataview.cpp @@ -0,0 +1,79 @@ +/* This file is part of the KDE project + 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 "kexitabledesigner_dataview.h" + +#include <kexidb/connection.h> +#include <kexidb/cursor.h> +#include <kexiutils/utils.h> +#include "kexitableview.h" +#include "kexidatatableview.h" +#include "keximainwindow.h" + +KexiTableDesigner_DataView::KexiTableDesigner_DataView(KexiMainWindow *win, QWidget *parent) + : KexiDataTable(win, parent, "KexiTableDesigner_DataView", true/*db-aware*/) +{ +} + +KexiTableDesigner_DataView::~KexiTableDesigner_DataView() +{ + if (dynamic_cast<KexiDataTableView*>(tableView()) + && dynamic_cast<KexiDataTableView*>(tableView())->cursor()) + { + mainWin()->project()->dbConnection()->deleteCursor( + dynamic_cast<KexiDataTableView*>(tableView())->cursor() ); + } +} + +tristate KexiTableDesigner_DataView::beforeSwitchTo(int mode, bool &dontStore) +{ + Q_UNUSED( dontStore ); + + if (mode != Kexi::DataViewMode) { + //accept editing before switching +// if (!m_view->acceptRowEdit()) { + if (!acceptRowEdit()) { + return cancelled; + } + } + + return true; +} + +tristate KexiTableDesigner_DataView::afterSwitchFrom(int mode) +{ + Q_UNUSED( mode ); + + if (tempData()->tableSchemaChangedInPreviousView) { + KexiUtils::WaitCursor wait; + KexiDB::Cursor *c = mainWin()->project()->dbConnection()->prepareQuery(*tempData()->table); + if (!c) + return false; + setData(c); + tempData()->tableSchemaChangedInPreviousView = false; + } + return true; +} + +KexiTablePart::TempData* KexiTableDesigner_DataView::tempData() const +{ + return static_cast<KexiTablePart::TempData*>(parentDialog()->tempData()); +} + +#include "kexitabledesigner_dataview.moc" diff --git a/kexi/plugins/tables/kexitabledesigner_dataview.h b/kexi/plugins/tables/kexitabledesigner_dataview.h new file mode 100644 index 00000000..59e84ab1 --- /dev/null +++ b/kexi/plugins/tables/kexitabledesigner_dataview.h @@ -0,0 +1,49 @@ +/* This file is part of the KDE project + 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 KEXITABLEDESIGNERDATAVIEW_H +#define KEXITABLEDESIGNERDATAVIEW_H + +#include <kexidatatable.h> +#include "kexitablepart.h" + +class KexiTableDesigner_DataView : public KexiDataTable +{ + Q_OBJECT + + public: + KexiTableDesigner_DataView(KexiMainWindow *win, QWidget *parent); + + virtual ~KexiTableDesigner_DataView(); + + KexiTablePart::TempData* tempData() const; + + protected: +// //! called just once from ctor +// void init(); +// void initActions(); +// //! called whenever data should be reloaded (on switching to this view mode) +// void initData(); + + virtual tristate beforeSwitchTo(int mode, bool &dontStore); + virtual tristate afterSwitchFrom(int mode); + +}; + +#endif diff --git a/kexi/plugins/tables/kexitabledesignercommands.cpp b/kexi/plugins/tables/kexitabledesignercommands.cpp new file mode 100644 index 00000000..ccbb181a --- /dev/null +++ b/kexi/plugins/tables/kexitabledesignercommands.cpp @@ -0,0 +1,281 @@ +/* This file is part of the KDE project + Copyright (C) 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 <qdom.h> +#include <qwidget.h> +#include <qlayout.h> +#include <qlabel.h> +#include <qsplitter.h> +#include <qmetaobject.h> + +#include <kdebug.h> +#include <klocale.h> +#include <kpopupmenu.h> +#include <kmessagebox.h> +#include <kaccelmanager.h> + +#include <koproperty/property.h> + +#include "kexitabledesignercommands.h" + +using namespace KexiTableDesignerCommands; + + +Command::Command(KexiTableDesignerView* view) + : KCommand() + , m_view(view) +{ +} + +Command::~Command() +{ +} + +//-------------------------------------------------------- + +ChangeFieldPropertyCommand::ChangeFieldPropertyCommand( KexiTableDesignerView* view, + const KoProperty::Set& set, const QCString& propertyName, const QVariant& oldValue, const QVariant& newValue, + KoProperty::Property::ListData* const oldListData, KoProperty::Property::ListData* const newListData) + : Command(view) + , m_alterTableAction( + propertyName=="name" ? oldValue.toString() : set.property("name").value().toString(), + propertyName, newValue, set["uid"].value().toInt()) + , m_oldValue(oldValue) +// , m_fieldUID(set["uid"].value().toInt()) + , m_oldListData( oldListData ? new KoProperty::Property::ListData(*oldListData) : 0 ) + , m_listData( newListData ? new KoProperty::Property::ListData(*newListData) : 0 ) +{ + kexipluginsdbg << "ChangeFieldPropertyCommand: " << debugString() << endl; +} + +ChangeFieldPropertyCommand::~ChangeFieldPropertyCommand() +{ + delete m_oldListData; + delete m_listData; +} + +QString ChangeFieldPropertyCommand::name() const +{ + return i18n("Change \"%1\" property for table field from \"%2\" to \"%3\"") + .arg(m_alterTableAction.propertyName()).arg(m_oldValue.toString()) + .arg(m_alterTableAction.newValue().toString()); +} + +QString ChangeFieldPropertyCommand::debugString() +{ + QString s( name() ); + if (m_oldListData || m_listData) + s += QString("\nAnd list data from [%1]\n to [%2]") + .arg( m_oldListData ? + QString("%1 -> %2") + .arg(m_oldListData->keysAsStringList().join(",")).arg(m_oldListData->names.join(",")) + : QString("<NONE>")) + .arg( m_listData ? + QString("%1 -> %2") + .arg(m_listData->keysAsStringList().join(",")).arg(m_listData->names.join(",")) + : QString("<NONE>")); + return s + QString(" (UID=%1)").arg(m_alterTableAction.uid()); +} + +void ChangeFieldPropertyCommand::execute() +{ + m_view->changeFieldProperty( + m_alterTableAction.uid(), + m_alterTableAction.propertyName().latin1(), + m_alterTableAction.newValue(), m_listData ); +} + +void ChangeFieldPropertyCommand::unexecute() +{ + m_view->changeFieldProperty( + m_alterTableAction.uid(), + m_alterTableAction.propertyName().latin1(), + m_oldValue, m_oldListData ); +} + +KexiDB::AlterTableHandler::ActionBase* ChangeFieldPropertyCommand::createAction() +{ + if (m_alterTableAction.propertyName()=="subType") {//skip these properties + return 0; + } + return new KexiDB::AlterTableHandler::ChangeFieldPropertyAction( m_alterTableAction ); +} + +//-------------------------------------------------------- + +RemoveFieldCommand::RemoveFieldCommand( KexiTableDesignerView* view, int fieldIndex, + const KoProperty::Set* set) + : Command(view) + , m_alterTableAction( set ? (*set)["name"].value().toString() : QString::null, + set ? (*set)["uid"].value().toInt() : -1 ) + , m_set( set ? new KoProperty::Set(*set /*deep copy*/) : 0 ) + , m_fieldIndex(fieldIndex) +{ +} + +RemoveFieldCommand::~RemoveFieldCommand() +{ + delete m_set; +} + +QString RemoveFieldCommand::name() const +{ + if (m_set) + return i18n("Remove table field \"%1\"").arg(m_alterTableAction.fieldName()); + + return QString("Remove empty row at position %1").arg(m_fieldIndex); +} + +void RemoveFieldCommand::execute() +{ +// m_view->deleteField( m_fieldIndex ); + m_view->deleteRow( m_fieldIndex ); +} + +void RemoveFieldCommand::unexecute() +{ + m_view->insertEmptyRow(m_fieldIndex); + if (m_set) + m_view->insertField( m_fieldIndex, *m_set ); +} + +QString RemoveFieldCommand::debugString() +{ + if (!m_set) + return name(); + + return name() + "\nAT ROW " + QString::number(m_fieldIndex) + + ", FIELD: " + (*m_set)["caption"].value().toString() + + QString(" (UID=%1)").arg(m_alterTableAction.uid()); +} + +KexiDB::AlterTableHandler::ActionBase* RemoveFieldCommand::createAction() +{ + return new KexiDB::AlterTableHandler::RemoveFieldAction( m_alterTableAction ); +} + +//-------------------------------------------------------- + +InsertFieldCommand::InsertFieldCommand( KexiTableDesignerView* view, + int fieldIndex/*, const KexiDB::Field& field*/, const KoProperty::Set& set ) + : Command(view) + , m_alterTableAction(0) //fieldIndex, new KexiDB::Field(field) /*deep copy*/) + , m_set( set ) //? new KoProperty::Set(*set) : 0 ) +{ + KexiDB::Field *f = view->buildField( m_set ); + if (f) + m_alterTableAction = new KexiDB::AlterTableHandler::InsertFieldAction( + fieldIndex, f, set["uid"].value().toInt()); + else //null action + m_alterTableAction = new KexiDB::AlterTableHandler::InsertFieldAction(true); +} + +InsertFieldCommand::~InsertFieldCommand() +{ + delete m_alterTableAction; +} + +QString InsertFieldCommand::name() const +{ + return i18n("Insert table field \"%1\"").arg(m_set["caption"].value().toString()); +} + +void InsertFieldCommand::execute() +{ + m_view->insertField( m_alterTableAction->index(), /*m_alterTableAction.field(),*/ m_set ); +} + +void InsertFieldCommand::unexecute() +{ + m_view->clearRow( m_alterTableAction->index() );//m_alterTableAction.index() ); +} + +KexiDB::AlterTableHandler::ActionBase* InsertFieldCommand::createAction() +{ + return new KexiDB::AlterTableHandler::InsertFieldAction(*m_alterTableAction); +} + +//-------------------------------------------------------- + +ChangePropertyVisibilityCommand::ChangePropertyVisibilityCommand( KexiTableDesignerView* view, + const KoProperty::Set& set, const QCString& propertyName, bool visible) + : Command(view) + , m_alterTableAction(set.property("name").value().toString(), propertyName, visible, set["uid"].value().toInt()) +// , m_fieldUID(set["uid"].value().toInt()) + , m_oldVisibility( set.property(propertyName).isVisible() ) +{ + kexipluginsdbg << "ChangePropertyVisibilityCommand: " << debugString() << endl; +} + +ChangePropertyVisibilityCommand::~ChangePropertyVisibilityCommand() +{ +} + +QString ChangePropertyVisibilityCommand::name() const +{ + return QString("[internal] Change \"%1\" visibility from \"%2\" to \"%3\"") + .arg(m_alterTableAction.propertyName()) + .arg(m_oldVisibility ? "true" : "false") + .arg(m_alterTableAction.newValue().toBool() ? "true" : "false"); +} + +void ChangePropertyVisibilityCommand::execute() +{ + m_view->changePropertyVisibility( + m_alterTableAction.uid(), + m_alterTableAction.propertyName().latin1(), + m_alterTableAction.newValue().toBool() ); +} + +void ChangePropertyVisibilityCommand::unexecute() +{ + m_view->changePropertyVisibility( + m_alterTableAction.uid(), + m_alterTableAction.propertyName().latin1(), + m_oldVisibility ); +} + +//-------------------------------------------------------- + +InsertEmptyRowCommand::InsertEmptyRowCommand( KexiTableDesignerView* view, int row ) + : Command(view) + , m_alterTableAction(true) //unused, null action + , m_row(row) +{ +} + +InsertEmptyRowCommand::~InsertEmptyRowCommand() +{ +} + +QString InsertEmptyRowCommand::name() const +{ + return QString("Insert empty row at position %1").arg(m_row); +} + +void InsertEmptyRowCommand::execute() +{ + m_view->insertEmptyRow( m_row ); +} + +void InsertEmptyRowCommand::unexecute() +{ + // let's assume the row is empty... + m_view->deleteRow( m_row ); +} + diff --git a/kexi/plugins/tables/kexitabledesignercommands.h b/kexi/plugins/tables/kexitabledesignercommands.h new file mode 100644 index 00000000..355aabe2 --- /dev/null +++ b/kexi/plugins/tables/kexitabledesignercommands.h @@ -0,0 +1,188 @@ +/* This file is part of the KDE project + Copyright (C) 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 KEXITABLEDESIGNER_COMMANDS_H +#define KEXITABLEDESIGNER_COMMANDS_H + +#include <qmap.h> +#include <qdict.h> +#include <qptrlist.h> +#include <qptrdict.h> +#include <qvariant.h> +#include <qguardedptr.h> + +#include <kcommand.h> +#include <kexidb/alter.h> +#include <koproperty/set.h> + +#include "kexitabledesignerview.h" + +class QWidget; +class QRect; +class QPoint; +class QStringList; +class QCString; + +namespace KexiTableDesignerCommands { + +//! @short Base class for all Table Designer's commands +class Command : public KCommand +{ + public: + Command(KexiTableDesignerView* view); + virtual ~Command(); + + //! Used to collect actions data for AlterTableHandler + //! Can return 0 if the action should not be passed to AlterTableHandler + virtual KexiDB::AlterTableHandler::ActionBase* createAction() { return 0; } + + virtual QString debugString() { return name(); } + + protected: + QGuardedPtr<KexiTableDesignerView> m_view; +}; + +//! @short Undo/redo command used for when changing a property for a table field +class ChangeFieldPropertyCommand : public Command +{ + public: + /*! Creates the ChangeFieldPropertyCommand object. + Note: we use internal "uid" property of a field (set["uid"]) to avoid problems with looking + for field by name when more than one field exists with the same name + (it's invalid but allowed in design time). + \a oldlistData and and \a newListData can be specified so Property::setListData() will be called + on execute() and unexecute(). + */ + ChangeFieldPropertyCommand( KexiTableDesignerView* view, + const KoProperty::Set& set, const QCString& propertyName, + const QVariant& oldValue, const QVariant& newValue, + KoProperty::Property::ListData* const oldListData = 0, KoProperty::Property::ListData* const newListData = 0); + + virtual ~ChangeFieldPropertyCommand(); + + virtual QString name() const; + virtual void execute(); + virtual void unexecute(); + virtual KexiDB::AlterTableHandler::ActionBase* createAction(); + virtual QString debugString(); + + protected: + KexiDB::AlterTableHandler::ChangeFieldPropertyAction m_alterTableAction; + QVariant m_oldValue; +// int m_fieldUID; + KoProperty::Property::ListData* m_oldListData, *m_listData; +}; + +//! @short Undo/redo command used when a field is removed from a table +class RemoveFieldCommand : public Command +{ + public: + /*! Constructs RemoveFieldCommand object. + If \a set is 0, the action only means removing empty row (internal). */ + RemoveFieldCommand( KexiTableDesignerView* view, int fieldIndex, + const KoProperty::Set* set); + + virtual ~RemoveFieldCommand(); + + virtual QString name() const; + virtual void execute(); + virtual void unexecute(); + virtual KexiDB::AlterTableHandler::ActionBase* createAction(); + + virtual QString debugString(); + + protected: + KexiDB::AlterTableHandler::RemoveFieldAction m_alterTableAction; + KoProperty::Set* m_set; + int m_fieldIndex; +}; + +//! @short Undo/redo command used when a new field is inserted into a table +class InsertFieldCommand : public Command +{ + public: + InsertFieldCommand( KexiTableDesignerView* view, + int fieldIndex/*, const KexiDB::Field& field*/, const KoProperty::Set& set ); + virtual ~InsertFieldCommand(); + + virtual QString name() const; + virtual void execute(); + virtual void unexecute(); + virtual KexiDB::AlterTableHandler::ActionBase* createAction(); + + virtual QString debugString() { + return name() + "\nAT ROW " + QString::number(m_alterTableAction->index()) //m_alterTableAction.index()) + + ", FIELD: " + m_set["caption"].value().toString(); //m_alterTableAction.field().debugString(); + } + + protected: + KexiDB::AlterTableHandler::InsertFieldAction *m_alterTableAction; + KoProperty::Set m_set; +}; + + +/* ---- Internal commands follow (not used for building performing ALTER TABLE ---- */ + +//! @short Undo/redo command used when property visibility is changed +/*! Internal, only used in addition to property change. */ +class ChangePropertyVisibilityCommand : public Command +{ + public: + /*! Creates the ChangePropertyVisibilityCommand object. + Note: we use internal "uid" property of a field (set["uid"]) to avoid problems with looking + for field by name when more than one field exists with the same name + (it's invalid but allowed in design time). + */ + ChangePropertyVisibilityCommand( KexiTableDesignerView* view, + const KoProperty::Set& set, const QCString& propertyName, + bool visible); + + virtual ~ChangePropertyVisibilityCommand(); + + virtual QString name() const; + virtual void execute(); + virtual void unexecute(); + + protected: + KexiDB::AlterTableHandler::ChangeFieldPropertyAction m_alterTableAction; +// int m_fieldUID; + bool m_oldVisibility; +}; + +//! @short Undo/redo command used when property visibility is changed +/*! Internal, only used in addition to property change. */ +class InsertEmptyRowCommand : public Command +{ + public: + /*! Creates the InsertEmptyRowCommand object. */ + InsertEmptyRowCommand( KexiTableDesignerView* view, int row ); + virtual ~InsertEmptyRowCommand(); + + virtual QString name() const; + virtual void execute(); + virtual void unexecute(); + + protected: + KexiDB::AlterTableHandler::ChangeFieldPropertyAction m_alterTableAction; + int m_row; +}; + +} + +#endif diff --git a/kexi/plugins/tables/kexitabledesignerview.cpp b/kexi/plugins/tables/kexitabledesignerview.cpp new file mode 100644 index 00000000..7e3478ed --- /dev/null +++ b/kexi/plugins/tables/kexitabledesignerview.cpp @@ -0,0 +1,1943 @@ +/* This file is part of the KDE project + Copyright (C) 2004-2007 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 "kexitabledesignerview.h" +#include "kexitabledesignerview_p.h" +#include "kexilookupcolumnpage.h" +#include "kexitabledesignercommands.h" + +#include <qlayout.h> +#include <qlabel.h> +#include <qsplitter.h> + +#include <kiconloader.h> +#include <kdebug.h> +#include <klocale.h> +#include <kaction.h> +#include <kpopupmenu.h> +#include <kmessagebox.h> +#include <kiconeffect.h> + +#include <koproperty/set.h> +#include <koproperty/utils.h> + +#include <kexidb/cursor.h> +#include <kexidb/tableschema.h> +#include <kexidb/connection.h> +#include <kexidb/utils.h> +#include <kexidb/roweditbuffer.h> +#include <kexidb/error.h> +#include <kexidb/lookupfieldschema.h> +#include <kexiutils/identifier.h> +#include <kexiproject.h> +#include <keximainwindow.h> +#include <widget/tableview/kexidataawarepropertyset.h> +#include <widget/kexicustompropertyfactory.h> +#include <kexiutils/utils.h> +#include <kexidialogbase.h> +#include <kexitableview.h> + +//#define MAX_FIELDS 101 //nice prime number + +//! used only for BLOBs +#define DEFAULT_OBJECT_TYPE_VALUE "image" + +//#define KexiTableDesignerView_DEBUG + +//! @todo remove this when BLOBs are implemented +//#define KEXI_NO_BLOB_FIELDS + +using namespace KexiTableDesignerCommands; + +//! @internal Used in tryCastQVariant() anf canCastQVariant() +static bool isIntegerQVariant(QVariant::Type t) +{ + return t==QVariant::LongLong + || t==QVariant::ULongLong + || t==QVariant::Int + || t==QVariant::UInt; +} + +//! @internal Used in tryCastQVariant() +static bool canCastQVariant(QVariant::Type fromType, QVariant::Type toType) +{ + return (fromType==QVariant::Int && toType==QVariant::UInt) + || (fromType==QVariant::CString && toType==QVariant::String) + || (fromType==QVariant::LongLong && toType==QVariant::ULongLong) + || ((fromType==QVariant::String || fromType==QVariant::CString) + && (isIntegerQVariant(toType) || toType==QVariant::Double)); +} + +/*! @internal + \return a variant value converted from \a fromVal to \a toType type. + Null QVariant is returned if \a fromVal's type and \a toType type + are incompatible. */ +static QVariant tryCastQVariant( const QVariant& fromVal, QVariant::Type toType ) +{ + const QVariant::Type fromType = fromVal.type(); + if (fromType == toType) + return fromVal; + if (canCastQVariant(fromType, toType) || canCastQVariant(toType, fromType) + || (isIntegerQVariant(fromType) && toType==QVariant::Double)) + { + QVariant res( fromVal ); + if (res.cast(toType)) + return res; + } + return QVariant(); +} + + +KexiTableDesignerView::KexiTableDesignerView(KexiMainWindow *win, QWidget *parent) + : KexiDataTable(win, parent, "KexiTableDesignerView", false/*not db-aware*/) + , KexiTableDesignerInterface() + , d( new KexiTableDesignerViewPrivate(this) ) +{ + //needed for custom "identifier" property editor widget + KexiCustomPropertyFactory::init(); + + KexiDB::Connection *conn = mainWin()->project()->dbConnection(); + d->view = dynamic_cast<KexiTableView*>(mainWidget()); + + d->data = new KexiTableViewData(); + if (conn->isReadOnly()) + d->data->setReadOnly(true); + d->data->setInsertingEnabled( false ); + + KexiTableViewColumn *col = new KexiTableViewColumn("pk", KexiDB::Field::Text, QString::null, + i18n("Additional information about the field")); + col->setIcon( KexiUtils::colorizeIconToTextColor( SmallIcon("info"), d->view->palette() ) ); + col->setHeaderTextVisible(false); + col->field()->setSubType("KIcon"); + col->setReadOnly(true); + d->data->addColumn( col ); + +// col = new KexiTableViewColumn("name", KexiDB::Field::Text, i18n("Field Name"), + col = new KexiTableViewColumn("caption", KexiDB::Field::Text, i18n("Field Caption"), + i18n("Describes caption for the field")); +// KexiUtils::Validator *vd = new KexiUtils::IdentifierValidator(); +// vd->setAcceptsEmptyValue(true); +// col->setValidator( vd ); + d->data->addColumn( col ); + + col = new KexiTableViewColumn("type", KexiDB::Field::Enum, i18n("Data Type"), + i18n("Describes data type for the field")); + d->data->addColumn( col ); + +#ifdef KEXI_NO_BLOB_FIELDS +//! @todo remove this later + QValueVector<QString> types(KexiDB::Field::LastTypeGroup-1); //don't show last type (BLOB) +#else + QValueVector<QString> types(KexiDB::Field::LastTypeGroup); +#endif + d->maxTypeNameTextWidth = 0; + QFontMetrics fm(font()); + for (uint i=1; i<=types.count(); i++) { + types[i-1] = KexiDB::Field::typeGroupName(i); + d->maxTypeNameTextWidth = QMAX(d->maxTypeNameTextWidth, fm.width(types[i-1])); + } + col->field()->setEnumHints(types); + + d->data->addColumn( col = new KexiTableViewColumn("comments", KexiDB::Field::Text, i18n("Comments"), + i18n("Describes additional comments for the field")) ); + + d->view->setSpreadSheetMode(); + + connect(d->data, SIGNAL(aboutToChangeCell(KexiTableItem*,int,QVariant&,KexiDB::ResultInfo*)), + this, SLOT(slotBeforeCellChanged(KexiTableItem*,int,QVariant&,KexiDB::ResultInfo*))); + connect(d->data, SIGNAL(rowUpdated(KexiTableItem*)), + this, SLOT(slotRowUpdated(KexiTableItem*))); + //connect(d->data, SIGNAL(aboutToInsertRow(KexiTableItem*,KexiDB::ResultInfo*,bool)), + // this, SLOT(slotAboutToInsertRow(KexiTableItem*,KexiDB::ResultInfo*,bool))); + connect(d->data, SIGNAL(aboutToDeleteRow(KexiTableItem&,KexiDB::ResultInfo*,bool)), + this, SLOT(slotAboutToDeleteRow(KexiTableItem&,KexiDB::ResultInfo*,bool))); + + setMinimumSize(d->view->minimumSizeHint().width(), d->view->minimumSizeHint().height()); + d->view->setFocus(); + + d->sets = new KexiDataAwarePropertySet( this, d->view ); + connect(d->sets, SIGNAL(rowDeleted()), this, SLOT(updateActions())); + connect(d->sets, SIGNAL(rowInserted()), this, SLOT(slotRowInserted())); + + d->contextMenuTitle = new KPopupTitle(d->view->contextMenu()); + d->view->contextMenu()->insertItem(d->contextMenuTitle, -1, 0); + connect(d->view->contextMenu(), SIGNAL(aboutToShow()), this, SLOT(slotAboutToShowContextMenu())); + + plugSharedAction("tablepart_toggle_pkey", this, SLOT(slotTogglePrimaryKey())); + d->action_toggle_pkey = static_cast<KToggleAction*>( sharedAction("tablepart_toggle_pkey") ); + d->action_toggle_pkey->plug(d->view->contextMenu(), 1); //add at the beginning + d->view->contextMenu()->insertSeparator(2); + setAvailable("tablepart_toggle_pkey", !conn->isReadOnly()); + +#ifndef KEXI_NO_UNDOREDO_ALTERTABLE + plugSharedAction("edit_undo", this, SLOT(slotUndo())); + plugSharedAction("edit_redo", this, SLOT(slotRedo())); + setAvailable("edit_undo", false); + setAvailable("edit_redo", false); + connect(d->history, SIGNAL(commandExecuted(KCommand*)), this, SLOT(slotCommandExecuted(KCommand*))); +#endif + +#ifdef KEXI_DEBUG_GUI + KexiUtils::addAlterTableActionDebug(QString::null); //to create the tab + KexiUtils::connectPushButtonActionForDebugWindow( + "simulateAlterTableExecution", this, SLOT(slotSimulateAlterTableExecution())); + KexiUtils::connectPushButtonActionForDebugWindow( + "executeRealAlterTable", this, SLOT(executeRealAlterTable())); +#endif +} + +KexiTableDesignerView::~KexiTableDesignerView() +{ +// removeCurrentPropertySet(); + delete d; +} + +void KexiTableDesignerView::initData() +{ + //add column data +// d->data->clear(); + d->data->deleteAllRows(); + int tableFieldCount = 0; + d->primaryKeyExists = false; + + if (tempData()->table) { + tableFieldCount = tempData()->table->fieldCount(); +//not needed d->sets->clear(tableFieldCount); + + //recreate table data rows + for(int i=0; i < tableFieldCount; i++) { + KexiDB::Field *field = tempData()->table->field(i); + KexiTableItem *item = d->data->createItem(); //new KexiTableItem(0); + if (field->isPrimaryKey()) { + (*item)[COLUMN_ID_ICON] = "key"; + d->primaryKeyExists = true; + } + else { + KexiDB::LookupFieldSchema *lookupFieldSchema + = field->table() ? field->table()->lookupFieldSchema(*field) : 0; + if (lookupFieldSchema && lookupFieldSchema->rowSource().type()!=KexiDB::LookupFieldSchema::RowSource::NoType + && !lookupFieldSchema->rowSource().name().isEmpty()) + { + (*item)[COLUMN_ID_ICON] = "combo"; + } + } + (*item)[COLUMN_ID_CAPTION] = field->captionOrName(); + (*item)[COLUMN_ID_TYPE] = field->typeGroup()-1; //-1 because type groups are counted from 1 + (*item)[COLUMN_ID_DESC] = field->description(); + d->data->append(item); + +//later! createPropertySet( i, field ); + } + } +// else { +// d->sets->clear();//default size +// } + + //add empty space +// const int columnsCount = d->data->columnsCount(); + for (int i=tableFieldCount; i<(int)d->sets->size(); i++) { +// KexiTableItem *item = new KexiTableItem(columnsCount);//3 empty fields + d->data->append(d->data->createItem()); + } + + //set data for our spreadsheet: this will clear our sets + d->view->setData(d->data); + + //now recreate property sets + if (tempData()->table) { + for(int i=0; i < tableFieldCount; i++) { + KexiDB::Field *field = tempData()->table->field(i); + createPropertySet( i, *field ); + } + } + + //column widths + d->view->setColumnWidth(COLUMN_ID_ICON, IconSize( KIcon::Small ) + 10); + d->view->adjustColumnWidthToContents(COLUMN_ID_CAPTION); //adjust column width + d->view->setColumnWidth(COLUMN_ID_TYPE, d->maxTypeNameTextWidth + 2 * d->view->rowHeight()); + d->view->setColumnStretchEnabled( true, COLUMN_ID_DESC ); //last column occupies the rest of the area + const int minCaptionColumnWidth = d->view->fontMetrics().width("wwwwwwwwwww"); + if (minCaptionColumnWidth > d->view->columnWidth(COLUMN_ID_CAPTION)) + d->view->setColumnWidth(COLUMN_ID_CAPTION, minCaptionColumnWidth); + + setDirty(false); + d->view->setCursorPosition(0, COLUMN_ID_CAPTION); //set @ name column + propertySetSwitched(); +} + +//! Gets subtype strings and names for type \a fieldType +void +KexiTableDesignerView::getSubTypeListData(KexiDB::Field::TypeGroup fieldTypeGroup, + QStringList& stringsList, QStringList& namesList) +{ +/* disabled - "mime" is moved from subType to "objectType" custom property + if (fieldTypeGroup==KexiDB::Field::BLOBGroup) { + // special case: BLOB type uses "mime-based" subtypes +//! @todo hardcoded! + stringsList << "image"; + namesList << i18n("Image object type", "Image"); + } + else {*/ + stringsList = KexiDB::typeStringsForGroup(fieldTypeGroup); + namesList = KexiDB::typeNamesForGroup(fieldTypeGroup); +// } + kexipluginsdbg << "KexiTableDesignerView::getSubTypeListData(): subType strings: " << + stringsList.join("|") << "\nnames: " << namesList.join("|") << endl; +} + +KoProperty::Set * +KexiTableDesignerView::createPropertySet( int row, const KexiDB::Field& field, bool newOne ) +{ + QString typeName = "KexiDB::Field::" + field.typeGroupString(); + KoProperty::Set *set = new KoProperty::Set(d->sets, typeName); + if (mainWin()->project()->dbConnection()->isReadOnly()) + set->setReadOnly( true ); +// connect(buff,SIGNAL(propertyChanged(KexiPropertyBuffer&,KexiProperty&)), +// this, SLOT(slotPropertyChanged(KexiPropertyBuffer&,KexiProperty&))); + + KoProperty::Property *prop; + + set->addProperty(prop = new KoProperty::Property("uid", d->generateUniqueId(), "")); + prop->setVisible(false); + + //meta-info for property editor + set->addProperty(prop = new KoProperty::Property("this:classString", i18n("Table field")) ); + prop->setVisible(false); + set->addProperty(prop = new KoProperty::Property("this:iconName", +//! \todo add table_field icon + "lineedit" //"table_field" + )); + prop->setVisible(false); + set->addProperty(prop = new KoProperty::Property("this:useCaptionAsObjectName", + QVariant(true, 1), QString::null)); //we want "caption" to be displayed in the header, not name + prop->setVisible(false); + + //name + set->addProperty(prop + = new KoProperty::Property("name", QVariant(field.name()), i18n("Name"), + QString::null, KexiCustomPropertyFactory::Identifier) ); + + //type + set->addProperty( prop + = new KoProperty::Property("type", QVariant(field.type()), i18n("Type")) ); +#ifndef KexiTableDesignerView_DEBUG + prop->setVisible(false);//always hidden +#endif + + //subtype + QStringList typeStringList, typeNameList; + getSubTypeListData(field.typeGroup(), typeStringList, typeNameList); +/* disabled - "mime" is moved from subType to "objectType" custom property + QString subTypeValue; + if (field.typeGroup()==KexiDB::Field::BLOBGroup) { +// special case: BLOB type uses "mime-based" subtypes +//! @todo this should be retrieved from KexiDB::Field when BLOB supports many different mimetypes + subTypeValue = slist.first(); + } + else {*/ + QString subTypeValue = field.typeString(); + //} + set->addProperty(prop = new KoProperty::Property("subType", + typeStringList, typeNameList, subTypeValue, i18n("Subtype"))); + + // objectType + QStringList objectTypeStringList, objectTypeNameList; +//! @todo this should be retrieved from KexiDB::Field when BLOB supports many different mimetypes + objectTypeStringList << "image"; + objectTypeNameList << i18n("Image object type", "Image"); + QString objectTypeValue( field.customProperty("objectType").toString() ); + if (objectTypeValue.isEmpty()) + objectTypeValue = DEFAULT_OBJECT_TYPE_VALUE; + set->addProperty(prop = new KoProperty::Property("objectType", + objectTypeStringList, objectTypeNameList, objectTypeValue, i18n("Subtype")/*todo other i18n string?*/)); + + set->addProperty( prop + = new KoProperty::Property("caption", QVariant(field.caption()), i18n("Caption") ) ); + prop->setVisible(false);//always hidden + + set->addProperty( prop + = new KoProperty::Property("description", QVariant(field.description())) ); + prop->setVisible(false);//always hidden + + set->addProperty(prop + = new KoProperty::Property("unsigned", QVariant(field.isUnsigned(), 4), i18n("Unsigned Number"))); + + set->addProperty( prop + = new KoProperty::Property("length", (int)field.length()/*200?*/, i18n("Length"))); + + set->addProperty( prop + = new KoProperty::Property("precision", (int)field.precision()/*200?*/, i18n("Precision"))); +#ifdef KEXI_NO_UNFINISHED + prop->setVisible(false); +#endif + set->addProperty( prop + = new KoProperty::Property("visibleDecimalPlaces", field.visibleDecimalPlaces(), i18n("Visible Decimal Places"))); + prop->setOption("min", -1); + prop->setOption("minValueText", i18n("Auto Decimal Places","Auto")); + +//! @todo set reasonable default for column width + set->addProperty( prop + = new KoProperty::Property("width", (int)field.width()/*200?*/, i18n("Column Width"))); +#ifdef KEXI_NO_UNFINISHED + prop->setVisible(false); +#endif + + set->addProperty( prop + = new KoProperty::Property("defaultValue", field.defaultValue(), i18n("Default Value"), + QString::null, +//! @todo use "Variant" type here when supported by KoProperty + (KoProperty::PropertyType)field.variantType()) ); + prop->setOption("3rdState", i18n("None")); +// prop->setVisible(false); + + set->addProperty( prop + = new KoProperty::Property("primaryKey", QVariant(field.isPrimaryKey(), 4), i18n("Primary Key"))); + prop->setIcon("key"); + + set->addProperty( prop + = new KoProperty::Property("unique", QVariant(field.isUniqueKey(), 4), i18n("Unique"))); + + set->addProperty( prop + = new KoProperty::Property("notNull", QVariant(field.isNotNull(), 4), i18n("Required"))); + + set->addProperty( prop + = new KoProperty::Property("allowEmpty", QVariant(!field.isNotEmpty(), 4), i18n("Allow Zero\nSize"))); + + set->addProperty( prop + = new KoProperty::Property("autoIncrement", QVariant(field.isAutoIncrement(), 4), i18n("Autonumber"))); + prop->setIcon("autonumber"); + + set->addProperty( prop + = new KoProperty::Property("indexed", QVariant(field.isIndexed(), 4), i18n("Indexed"))); + + //- properties related to lookup columns (used and set by the "lookup column" tab in the property pane) + KexiDB::LookupFieldSchema *lookupFieldSchema = field.table() ? field.table()->lookupFieldSchema(field) : 0; + set->addProperty( prop = new KoProperty::Property("rowSource", + lookupFieldSchema ? lookupFieldSchema->rowSource().name() : QString::null, i18n("Row Source"))); + prop->setVisible(false); + + set->addProperty( prop = new KoProperty::Property("rowSourceType", + lookupFieldSchema ? lookupFieldSchema->rowSource().typeName() : QString::null, i18n("Row Source\nType"))); + prop->setVisible(false); + + set->addProperty( prop + = new KoProperty::Property("boundColumn", + lookupFieldSchema ? lookupFieldSchema->boundColumn() : -1, i18n("Bound Column"))); + prop->setVisible(false); + +//! @todo this is backward-compatible code for "single visible column" implementation +//! for multiple columns, only the first is displayed, so there is a data loss is GUI is used +//! -- special koproperty editor needed + int visibleColumn = -1; + if (lookupFieldSchema && !lookupFieldSchema->visibleColumns().isEmpty()) + visibleColumn = lookupFieldSchema->visibleColumns().first(); + set->addProperty( prop + = new KoProperty::Property("visibleColumn", visibleColumn, i18n("Visible Column"))); + prop->setVisible(false); + +//! @todo support columnWidths(), columnHeadersVisible(), maximumListRows(), limitToList(), displayWidget() + + //---- + d->updatePropertiesVisibility(field.type(), *set); + + connect(set, SIGNAL(propertyChanged(KoProperty::Set&, KoProperty::Property&)), + this, SLOT(slotPropertyChanged(KoProperty::Set&, KoProperty::Property&))); + + d->sets->insert(row, set, newOne); + return set; +} + +void KexiTableDesignerView::updateActions(bool activated) +{ + Q_UNUSED(activated); +/*! \todo check if we can set pkey for this column type (eg. BLOB?) */ + setAvailable("tablepart_toggle_pkey", propertySet()!=0 && !mainWin()->project()->dbConnection()->isReadOnly()); + if (!propertySet()) + return; + KoProperty::Set &set = *propertySet(); + d->slotTogglePrimaryKeyCalled = true; + d->action_toggle_pkey->setChecked(set["primaryKey"].value().toBool()); + d->slotTogglePrimaryKeyCalled = false; +} + +void KexiTableDesignerView::slotUpdateRowActions(int row) +{ + KexiDataTable::slotUpdateRowActions(row); + updateActions(); +} + +void KexiTableDesignerView::slotTogglePrimaryKey() +{ + if (d->slotTogglePrimaryKeyCalled) + return; + d->slotTogglePrimaryKeyCalled = true; + if (!propertySet()) + return; + KoProperty::Set &set = *propertySet(); + bool isSet = !set["primaryKey"].value().toBool(); + set.changeProperty("primaryKey", QVariant(isSet,1)); //this will update all related properties as well +/* CommandGroup *setPrimaryKeyCommand; + if (isSet) { + setPrimaryKeyCommand = new CommandGroup(i18n("Set primary key for field \"%1\"") + .arg(set["name"].value().toString()) ); + } + else { + setPrimaryKeyCommand = new CommandGroup(i18n("Unset primary key for field \"%1\"") + .arg(set["name"].value().toString()) ); + } + switchPrimaryKey(set, isSet, false, setPrimaryKeyCommand);*/ + //addHistoryCommand( setPrimaryKeyCommand, false /* !execute */ ); + d->slotTogglePrimaryKeyCalled = false; +} + +void KexiTableDesignerView::switchPrimaryKey(KoProperty::Set &propertySet, + bool set, bool aWasPKey, CommandGroup* commandGroup) +{ + const bool was_pkey = aWasPKey || propertySet["primaryKey"].value().toBool(); +// propertySet["primaryKey"] = QVariant(set, 1); + d->setPropertyValueIfNeeded( propertySet, "primaryKey", QVariant(set,1), commandGroup ); + if (&propertySet==this->propertySet()) { + //update action and icon @ column 0 (only if we're changing current property set) + d->action_toggle_pkey->setChecked(set); + if (d->view->selectedItem()) { + //show key in the table + d->view->data()->clearRowEditBuffer(); + d->view->data()->updateRowEditBuffer(d->view->selectedItem(), COLUMN_ID_ICON, + QVariant(set ? "key" : "")); + d->view->data()->saveRowChanges(*d->view->selectedItem(), true); + } + if (was_pkey || set) //change flag only if we're setting pk or really clearing it + d->primaryKeyExists = set; + } + + if (set) { + //primary key is set, remove old pkey if exists + KoProperty::Set *s = 0; + int i; + const int count = (int)d->sets->size(); + for (i=0; i<count; i++) { + s = d->sets->at(i); + if (s && s!=&propertySet && (*s)["primaryKey"].value().toBool() && i!=d->view->currentRow()) + break; + } + if (i<count) {//remove + //(*s)["autoIncrement"] = QVariant(false, 0); + d->setPropertyValueIfNeeded( *s, "autoIncrement", QVariant(false,0), commandGroup ); + //(*s)["primaryKey"] = QVariant(false, 0); + d->setPropertyValueIfNeeded( *s, "primaryKey", QVariant(false,0), commandGroup ); + //remove key from table + d->view->data()->clearRowEditBuffer(); + KexiTableItem *item = d->view->itemAt(i); + if (item) { + d->view->data()->updateRowEditBuffer(item, COLUMN_ID_ICON, QVariant()); + d->view->data()->saveRowChanges(*item, true); + } + } + //set unsigned big-integer type +// d->view->data()->saveRowChanges(*d->view->selectedItem()); + d->slotBeforeCellChanged_enabled = false; + d->view->data()->clearRowEditBuffer(); + d->view->data()->updateRowEditBuffer(d->view->selectedItem(), COLUMN_ID_TYPE, + QVariant(KexiDB::Field::IntegerGroup-1/*counting from 0*/)); +// QVariant(KexiDB::Field::typeGroupName(KexiDB::Field::IntegerGroup))); + d->view->data()->saveRowChanges(*d->view->selectedItem(), true); + //propertySet["subType"] = KexiDB::Field::typeString(KexiDB::Field::BigInteger); + d->setPropertyValueIfNeeded( propertySet, "subType", KexiDB::Field::typeString(KexiDB::Field::BigInteger), + commandGroup ); + //propertySet["unsigned"] = QVariant(true,4); + d->setPropertyValueIfNeeded( propertySet, "unsigned", QVariant(true,4), commandGroup ); +/*todo*/ + d->slotBeforeCellChanged_enabled = true; + } + updateActions(); +} + +/*void KexiTableDesignerView::slotCellSelected(int, int row) +{ + kdDebug() << "KexiTableDesignerView::slotCellSelected()" << endl; + if(row == m_row) + return; + m_row = row; + propertyBufferSwitched(); +}*/ + +tristate KexiTableDesignerView::beforeSwitchTo(int mode, bool &dontStore) +{ + if (!d->view->acceptRowEdit()) + return false; +/* if (mode==Kexi::DesignViewMode) { + initData(); + return true; + } + else */ + tristate res = true; + if (mode==Kexi::DataViewMode) { + if (!dirty() && parentDialog()->neverSaved()) { + KMessageBox::sorry(this, i18n("Cannot switch to data view, because table design is empty.\n" + "First, please create your design.") ); + return cancelled; + } +//<temporary> + else if (dirty() && !parentDialog()->neverSaved()) { +// cancelled = (KMessageBox::No == KMessageBox::questionYesNo(this, i18n("Saving changes for existing table design is not yet supported.\nDo you want to discard your changes now?"))); + +// KexiDB::Connection *conn = mainWin()->project()->dbConnection(); + bool emptyTable; + int r = KMessageBox::warningYesNoCancel(this, + i18n("Saving changes for existing table design is now required.") + + "\n" + d->messageForSavingChanges(emptyTable, /* skip warning? */!isPhysicalAlteringNeeded()), + QString::null, + KStdGuiItem::save(), KStdGuiItem::discard(), QString::null, + KMessageBox::Notify|KMessageBox::Dangerous); + if (r == KMessageBox::Cancel) + res = cancelled; + else + res = true; + dontStore = (r!=KMessageBox::Yes); + if (!dontStore) + d->dontAskOnStoreData = true; +// if (dontStore) +// setDirty(false); + } +//</temporary> + //todo + return res; + } + else if (mode==Kexi::TextViewMode) { + //todo + } + return res; +} + +tristate KexiTableDesignerView::afterSwitchFrom(int mode) +{ + if (mode==Kexi::NoViewMode || mode==Kexi::DataViewMode) { + initData(); + } + return true; +} + +KoProperty::Set *KexiTableDesignerView::propertySet() +{ + return d->sets ? d->sets->currentPropertySet() : 0; +} + +/* +void KexiTableDesignerView::removeCurrentPropertySet() +{ + const int r = d->view->currentRow(); + KoProperty::Set *buf = d->sets.at(r); + if (!buf) + return; + buf->debug(); +// m_currentBufferCleared = true; + d->sets.remove(r); + propertysetswitched(); +// delete buf; +// m_currentBufferCleared = false; +} +*/ + +void KexiTableDesignerView::slotBeforeCellChanged( + KexiTableItem *item, int colnum, QVariant& newValue, KexiDB::ResultInfo* /*result*/) +{ + if (!d->slotBeforeCellChanged_enabled) + return; +// kdDebug() << d->view->selectedItem() << " " << item + //<< " " << d->sets->at( d->view->currentRow() ) << " " << propertySet() << endl; + if (colnum==COLUMN_ID_CAPTION) {//'caption' +// if (!item->at(1).toString().isEmpty() && item->at(1).isNull()) { + //if 'type' is not filled yet + if (item->at(COLUMN_ID_TYPE).isNull()) { + //auto select 1st row of 'type' column + d->view->data()->updateRowEditBuffer(item, COLUMN_ID_TYPE, QVariant((int)0)); + } + + KoProperty::Set *propertySetForItem = d->sets->findPropertySetForItem(*item); + if (propertySetForItem) { + d->addHistoryCommand_in_slotPropertyChanged_enabled = false; //because we'll add the two changes as one KMacroCommand + QString oldName( propertySetForItem->property("name").value().toString() ); + QString oldCaption( propertySetForItem->property("caption").value().toString() ); + + //we need to create the action now as set["name"] will be changed soon.. + ChangeFieldPropertyCommand *changeCaptionCommand + = new ChangeFieldPropertyCommand( this, *propertySetForItem, "caption", oldCaption, newValue); + + //update field caption and name + propertySetForItem->changeProperty("caption", newValue); + propertySetForItem->changeProperty("name", newValue); // "name" prop. is of custom type Identifier, so this assignment + // will automatically convert newValue to an valid identifier + + //remember this action containing 2 subactions + CommandGroup *changeCaptionAndNameCommand = new CommandGroup( + i18n("Change \"%1\" field's name to \"%2\" and caption from \"%3\" to \"%4\"") + .arg(oldName).arg(propertySetForItem->property("name").value().toString()) + .arg(oldCaption).arg(newValue.toString() )); + changeCaptionAndNameCommand->addCommand( changeCaptionCommand ); +// new ChangeFieldPropertyCommand( this, *propertySetForItem, + // "caption", oldCaption, newValue) + // ); + changeCaptionAndNameCommand->addCommand( + new ChangeFieldPropertyCommand( this, *propertySetForItem, + "name", oldName, propertySetForItem->property("name").value().toString()) + ); + addHistoryCommand( changeCaptionAndNameCommand, false /* !execute */ ); + + d->addHistoryCommand_in_slotPropertyChanged_enabled = true; + } + } + else if (colnum==COLUMN_ID_TYPE) {//'type' + if (newValue.isNull()) { + //'type' col will be cleared: clear all other columns as well + d->slotBeforeCellChanged_enabled = false; + d->view->data()->updateRowEditBuffer(item, COLUMN_ID_ICON, QVariant()); + d->view->data()->updateRowEditBuffer(item, COLUMN_ID_CAPTION, QVariant(QString::null)); + d->view->data()->updateRowEditBuffer(item, COLUMN_ID_DESC, QVariant()); + d->slotBeforeCellChanged_enabled = true; + return; + } + + KoProperty::Set *propertySetForItem = d->sets->findPropertySetForItem(*item); + if (!propertySetForItem) + return; + + KoProperty::Set &set = *propertySetForItem; //propertySet(); + + //'type' col is changed (existed before) + //-get type group number + KexiDB::Field::TypeGroup fieldTypeGroup; + int i_fieldTypeGroup = newValue.toInt()+1/*counting from 1*/; + if (i_fieldTypeGroup < 1 || i_fieldTypeGroup > +#ifdef KEXI_NO_BLOB_FIELDS +//! @todo remove this later + (int)KexiDB::Field::LastTypeGroup-1) //don't show last (BLOB) type +#else + (int)KexiDB::Field::LastTypeGroup) +#endif + return; + fieldTypeGroup = static_cast<KexiDB::Field::TypeGroup>(i_fieldTypeGroup); + + //-get 1st type from this group, and update 'type' property + KexiDB::Field::Type fieldType = KexiDB::defaultTypeForGroup( fieldTypeGroup ); + if (fieldType==KexiDB::Field::InvalidType) + fieldType = KexiDB::Field::Text; +//moved down set["type"] = (int)fieldType; +// set["subType"] = KexiDB::Field::typeName(fieldType); + + //-get subtypes for this type: keys (slist) and names (nlist) + QStringList slist, nlist; + getSubTypeListData(fieldTypeGroup, slist, nlist); + + QString subTypeValue; +/* disabled - "mime" is moved from subType to "objectType" custom property + if (fieldType==KexiDB::Field::BLOB) { + // special case: BLOB type uses "mime-based" subtypes + subTypeValue = slist.first(); + } + else {*/ + subTypeValue = KexiDB::Field::typeString(fieldType); + //} + KoProperty::Property *subTypeProperty = &set["subType"]; + kexipluginsdbg << subTypeProperty->value() << endl; + + // *** this action contains subactions *** + CommandGroup *changeDataTypeCommand = new CommandGroup( + i18n("Change data type for field \"%1\" to \"%2\"") + .arg(set["name"].value().toString()).arg( KexiDB::Field::typeName( fieldType ) ) ); + +//kexipluginsdbg << "++++++++++" << slist << nlist << endl; + + //update subtype list and value + const bool forcePropertySetReload + = KexiDB::Field::typeGroup( KexiDB::Field::typeForString(subTypeProperty->value().toString()) ) + != fieldTypeGroup; //<-- ????? +// const bool forcePropertySetReload = set["type"].value().toInt() != (int)fieldTypeGroup; + const bool useListData = slist.count() > 1; //disabled-> || fieldType==KexiDB::Field::BLOB; + + if (!useListData) { + slist.clear(); //empty list will be passed + nlist.clear(); + } + d->setPropertyValueIfNeeded( set, "type", (int)fieldType, changeDataTypeCommand, + false /*!forceAddCommand*/, true /*rememberOldValue*/); + + // notNull and defaultValue=false is reasonable for boolean type + if (fieldType == KexiDB::Field::Boolean) { +//! @todo maybe this is good for other data types as well? + d->setPropertyValueIfNeeded( set, "notNull", QVariant(true, 1), changeDataTypeCommand, + false /*!forceAddCommand*/, false /*!rememberOldValue*/); + d->setPropertyValueIfNeeded( set, "defaultValue", QVariant(false, 1), changeDataTypeCommand, + false /*!forceAddCommand*/, false /*!rememberOldValue*/); + } + +/* if (useListData) { + { + subTypeProperty->setListData( slist, nlist ); + } + else { + subTypeProperty->setListData( 0 ); + }*/ + if (set["primaryKey"].value().toBool()==true) { + //primary keys require big int, so if selected type is not integer- remove PK + if (fieldTypeGroup != KexiDB::Field::IntegerGroup) { + /*not needed, line below will do the work + d->view->data()->updateRowEditBuffer(item, COLUMN_ID_ICON, QVariant()); + d->view->data()->saveRowChanges(*item); */ + //set["primaryKey"] = QVariant(false, 1); + d->setPropertyValueIfNeeded( set, "primaryKey", QVariant(false, 1), changeDataTypeCommand ); +//! @todo should we display (passive?) dialog informing about cleared pkey? + } + } +// if (useListData) +// subTypeProperty->setValue( subTypeValue, false/*!rememberOldValue*/ ); + d->setPropertyValueIfNeeded( set, "subType", subTypeValue, + changeDataTypeCommand, false, false /*!rememberOldValue*/, + &slist, &nlist ); + + if (d->updatePropertiesVisibility(fieldType, set, changeDataTypeCommand) || forcePropertySetReload) { + //properties' visiblility changed: refresh prop. set + propertySetReloaded(true); + } + + addHistoryCommand( changeDataTypeCommand, false /* !execute */ ); + } + else if (colnum==COLUMN_ID_DESC) {//'description' + KoProperty::Set *propertySetForItem = d->sets->findPropertySetForItem(*item); + if (!propertySetForItem) + return; + //update field desc. + QVariant oldValue((*propertySetForItem)["description"].value()); + kexipluginsdbg << oldValue << endl; + propertySetForItem->changeProperty("description", newValue); + /*moved addHistoryCommand( + new ChangeFieldPropertyCommand( this, *propertySetForItem, + "description", oldValue, newValue ), false);*/ + } +} + +void KexiTableDesignerView::slotRowUpdated(KexiTableItem *item) +{ + const int row = d->view->data()->findRef(item); + if (row < 0) + return; + + setDirty(); + + //-check if the row was empty before updating + //if yes: we want to add a property set for this new row (field) + QString fieldCaption( item->at(COLUMN_ID_CAPTION).toString() ); + const bool prop_set_allowed = !item->at(COLUMN_ID_TYPE).isNull(); + + if (!prop_set_allowed && d->sets->at(row)/*propertySet()*/) { + //there is a property set, but it's not allowed - remove it: + d->sets->remove( row ); //d->sets->removeCurrentPropertySet(); + + //clear 'type' column: + d->view->data()->clearRowEditBuffer(); +// d->view->data()->updateRowEditBuffer(d->view->selectedItem(), COLUMN_ID_TYPE, QVariant()); + d->view->data()->updateRowEditBuffer(item, COLUMN_ID_TYPE, QVariant()); + d->view->data()->saveRowChanges(*item); + + } else if (prop_set_allowed && !d->sets->at(row)/*propertySet()*/) { + //-- create a new field: + KexiDB::Field::TypeGroup fieldTypeGroup = static_cast<KexiDB::Field::TypeGroup>( + item->at(COLUMN_ID_TYPE).toInt()+1/*counting from 1*/ ); + int intFieldType = KexiDB::defaultTypeForGroup( fieldTypeGroup ); + if (intFieldType==0) + return; + + QString description( item->at(COLUMN_ID_DESC).toString() ); + +//todo: check uniqueness: + QString fieldName( KexiUtils::string2Identifier(fieldCaption) ); + + KexiDB::Field::Type fieldType = KexiDB::intToFieldType( intFieldType ); + KexiDB::Field field( //tmp + fieldName, + fieldType, + KexiDB::Field::NoConstraints, + KexiDB::Field::NoOptions, + /*length*/0, + /*precision*/0, + /*defaultValue*/QVariant(), + fieldCaption, + description, + /*width*/0); +// m_newTable->addField( field ); + + // reasonable case for boolean type: set notNull flag and "false" as default value + if (fieldType == KexiDB::Field::Boolean) { + field.setNotNull( true ); + field.setDefaultValue( QVariant(false, 0) ); + } + + kexipluginsdbg << "KexiTableDesignerView::slotRowUpdated(): " << field.debugString() << endl; + + //create a new property set: + KoProperty::Set *newSet = createPropertySet( row, field, true ); +//moved + //add a special property indicating that this is brand new buffer, + //not just changed +// KoProperty::Property* prop = new KoProperty::Property("newrow", QVariant()); +// prop->setVisible(false); +// newbuff->addProperty( prop ); + + //refresh property editor: + propertySetSwitched(); + + if (row>=0) { + if (d->addHistoryCommand_in_slotRowUpdated_enabled) { + addHistoryCommand( new InsertFieldCommand( this, row, *newSet /*propertySet()*/ ), //, field /*will be copied*/ + false /* !execute */ ); + } + } + else { + kexipluginswarn << "KexiTableDesignerView::slotRowUpdated() row # not found !" << endl; + } + } +} + +void KexiTableDesignerView::updateActions() +{ + updateActions(false); +} + +void KexiTableDesignerView::slotPropertyChanged(KoProperty::Set& set, KoProperty::Property& property) +{ +// if (!d->slotPropertyChanged_enabled) +// return; + const QCString pname = property.name(); + kexipluginsdbg << "KexiTableDesignerView::slotPropertyChanged(): " << pname << " = " << property.value() + << " (oldvalue = " << property.oldValue() << ")" << endl; + + // true is PK should be altered + bool changePrimaryKey = false; + // true is PK should be set to true, otherwise unset + bool setPrimaryKey = false; + + if (pname=="primaryKey" && d->slotPropertyChanged_primaryKey_enabled) { + changePrimaryKey = true; + setPrimaryKey = property.value().toBool(); + } + + // update "lookup column" icon + if (pname=="rowSource" || pname=="rowSourceType") { +//! @todo indicate invalid definitions of lookup columns as well using a special icon +//! (e.g. due to missing data source) + const int row = d->sets->findRowForPropertyValue("uid", set["uid"].value().toInt()); + KexiTableItem *item = d->view->itemAt(row); + if (item) + d->updateIconForItem(*item, set); + } + + //setting autonumber requires setting PK as well + CommandGroup *setAutonumberCommand = 0; + CommandGroup *toplevelCommand = 0; + if (pname=="autoIncrement" && property.value().toBool()==true) { + if (set["primaryKey"].value().toBool()==false) {//we need PKEY here! + QString msg = QString("<p>") + +i18n("Setting autonumber requires primary key to be set for current field.")+"</p>"; + if (d->primaryKeyExists) + msg += (QString("<p>")+ i18n("Previous primary key will be removed.")+"</p>"); + msg += (QString("<p>") + +i18n("Do you want to create primary key for current field? " + "Click \"Cancel\" to cancel setting autonumber.")+"</p>"); + + if (KMessageBox::Yes == KMessageBox::questionYesNo(this, msg, + i18n("Setting Autonumber Field"), + KGuiItem(i18n("Create &Primary Key"), "key"), KStdGuiItem::cancel() )) + { + changePrimaryKey = true; + setPrimaryKey = true; + //switchPrimaryKey(set, true); + // this will be toplevel command + setAutonumberCommand = new CommandGroup( + i18n("Assign autonumber for field \"%1\"").arg(set["name"].value().toString()) ); + toplevelCommand = setAutonumberCommand; + d->setPropertyValueIfNeeded( set, "autoIncrement", QVariant(true,1), setAutonumberCommand ); + } + else { + setAutonumberCommand = new CommandGroup( + i18n("Remove autonumber from field \"%1\"").arg(set["name"].value().toString()) ); + //d->slotPropertyChanged_enabled = false; +// set["autoIncrement"].setValue( QVariant(false,1), false/*don't save old*/); +// d->slotPropertyChanged_enabled = true; + d->setPropertyValueIfNeeded( set, "autoIncrement", QVariant(false,1), setAutonumberCommand, + true /*forceAddCommand*/, false/*rememberOldValue*/ ); + addHistoryCommand( setAutonumberCommand, false /* !execute */ ); + return; + } + } + } + + //clear PK when these properties were set to false: + if ((pname=="indexed" || pname=="unique" || pname=="notNull") + && set["primaryKey"].value().toBool() && property.value().toBool()==false) + { +//! @todo perhaps show a hint in help panel telling what happens? + changePrimaryKey = true; + setPrimaryKey = false; + // this will be toplevel command + CommandGroup *unsetIndexedOrUniquOrNotNullCommand = new CommandGroup( + i18n("Set \"%1\" property for field \"%2\"").arg(property.caption()).arg(set["name"].value().toString()) ); + toplevelCommand = unsetIndexedOrUniquOrNotNullCommand; + d->setPropertyValueIfNeeded( set, pname, QVariant(false,1), unsetIndexedOrUniquOrNotNullCommand ); + if (pname=="notNull") { +//? d->setPropertyValueIfNeeded( set, "notNull", QVariant(true,1), unsetIndexedOrUniquOrNotNullCommand ); + d->setPropertyValueIfNeeded( set, "unique", QVariant(false,1), unsetIndexedOrUniquOrNotNullCommand ); + } + } + + if (pname=="defaultValue") { + KexiDB::Field::Type type = KexiDB::intToFieldType( set["type"].value().toInt() ); + set["defaultValue"].setType((KoProperty::PropertyType)KexiDB::Field::variantType(type)); + } + + if (pname=="subType" && d->slotPropertyChanged_subType_enabled) { + d->slotPropertyChanged_subType_enabled = false; + if (set["primaryKey"].value().toBool()==true + && property.value().toString()!=KexiDB::Field::typeString(KexiDB::Field::BigInteger)) + { + kexipluginsdbg << "INVALID " << property.value().toString() << endl; +// if (KMessageBox::Yes == KMessageBox::questionYesNo(this, msg, +// i18n("This field has promary key assigned. Setting autonumber field"), +// KGuiItem(i18n("Create &Primary Key"), "key"), KStdGuiItem::cancel() )) + + } + KexiDB::Field::Type type = KexiDB::intToFieldType( set["type"].value().toInt() ); + QString typeName; +/* disabled - "mime" is moved from subType to "objectType" custom property + if (type==KexiDB::Field::BLOB) { //special case + //find i18n'd text + QStringList stringsList, namesList; + getSubTypeListData(KexiDB::Field::BLOBGroup, stringsList, namesList); + const int stringIndex = stringsList.findIndex( property.value().toString() ); + if (-1 == stringIndex || stringIndex>=(int)namesList.count()) + typeName = property.value().toString(); //for sanity + else + typeName = namesList[stringIndex]; + } + else {*/ + typeName = KexiDB::Field::typeName( KexiDB::Field::typeForString(property.value().toString()) ); +// } +// kdDebug() << property.value().toString() << endl; +// kdDebug() << set["type"].value() << endl; +// if (KexiDB::Field::typeGroup( set["type"].value().toInt() ) == (int)KexiDB::Field::TextGroup) { + CommandGroup* changeFieldTypeCommand = new CommandGroup( + i18n("Change type for field \"%1\" to \"%2\"").arg(set["name"].value().toString()) + .arg(typeName) ); + d->setPropertyValueIfNeeded( set, "subType", property.value(), property.oldValue(), + changeFieldTypeCommand ); + + kexipluginsdbg << set["type"].value() << endl; + const KexiDB::Field::Type newType = KexiDB::Field::typeForString(property.value().toString()); + set["type"].setValue( newType ); + + // cast "defaultValue" property value to a new type + QVariant oldDefVal( set["defaultValue"].value() ); + QVariant newDefVal( tryCastQVariant(oldDefVal, KexiDB::Field::variantType(type)) ); + if (oldDefVal.type()!=newDefVal.type()) + set["defaultValue"].setType( newDefVal.type() ); + d->setPropertyValueIfNeeded( set, "defaultValue", newDefVal, newDefVal, + changeFieldTypeCommand ); + + d->updatePropertiesVisibility(newType, set); + //properties' visiblility changed: refresh prop. set + propertySetReloaded(true); + d->slotPropertyChanged_subType_enabled = true; + + addHistoryCommand( changeFieldTypeCommand, false /* !execute */ ); + return; +// } +// d->slotPropertyChanged_subType_enabled = true; +// return; + } + + if (d->addHistoryCommand_in_slotPropertyChanged_enabled && !changePrimaryKey/*we'll add multiple commands for PK*/) { + addHistoryCommand( new ChangeFieldPropertyCommand(this, set, + property.name(), property.oldValue() /* ??? */, property.value()), + false /* !execute */ ); + } + + if (changePrimaryKey) { + d->slotPropertyChanged_primaryKey_enabled = false; + if (setPrimaryKey) { + //primary key implies some rules + //const bool prev_addHistoryCommand_in_slotPropertyChanged_enabled = d->addHistoryCommand_in_slotPropertyChanged_enabled; +// d->addHistoryCommand_in_slotPropertyChanged_enabled = false; + + //this action contains subactions + CommandGroup *setPrimaryKeyCommand = new CommandGroup( + i18n("Set primary key for field \"%1\"") + .arg(set["name"].value().toString()) ); + if (toplevelCommand) + toplevelCommand->addCommand( setPrimaryKeyCommand ); + else + toplevelCommand = setPrimaryKeyCommand; + + d->setPropertyValueIfNeeded( set, "primaryKey", QVariant(true,1), setPrimaryKeyCommand, true /*forceAddCommand*/ ); + d->setPropertyValueIfNeeded( set, "unique", QVariant(true,1), setPrimaryKeyCommand ); + d->setPropertyValueIfNeeded( set, "notNull", QVariant(true,1), setPrimaryKeyCommand ); + d->setPropertyValueIfNeeded( set, "allowEmpty", QVariant(false,1), setPrimaryKeyCommand ); + d->setPropertyValueIfNeeded( set, "indexed", QVariant(true,1), setPrimaryKeyCommand ); +//! \todo: add setting for this: "Integer PKeys have autonumber set by default" + d->setPropertyValueIfNeeded( set, "autoIncrement", QVariant(true,1), setPrimaryKeyCommand ); + +/* set["unique"] = QVariant(true,1); + set["notNull"] = QVariant(true,1); + set["allowEmpty"] = QVariant(false,1); + set["indexed"] = QVariant(true,1); + set["autoIncrement"] = QVariant(true,1);*/ +// d->addHistoryCommand_in_slotPropertyChanged_enabled = prev_addHistoryCommand_in_slotPropertyChanged_enabled; +//down addHistoryCommand( toplevelCommand, false /* !execute */ ); + } + else {//! set PK to false + //remember this action containing 2 subactions + CommandGroup *setPrimaryKeyCommand = new CommandGroup( + i18n("Unset primary key for field \"%1\"") + .arg(set["name"].value().toString()) ); + if (toplevelCommand) + toplevelCommand->addCommand( setPrimaryKeyCommand ); + else + toplevelCommand = setPrimaryKeyCommand; + + d->setPropertyValueIfNeeded( set, "primaryKey", QVariant(false,1), setPrimaryKeyCommand, true /*forceAddCommand*/ ); + d->setPropertyValueIfNeeded( set, "autoIncrement", QVariant(false,1), setPrimaryKeyCommand ); +// set["autoIncrement"] = QVariant(false,1); + +//down addHistoryCommand( toplevelCommand, false /* !execute */ ); + } + switchPrimaryKey(set, setPrimaryKey, true/*wasPKey*/, toplevelCommand); + d->updatePropertiesVisibility( + KexiDB::Field::typeForString( set["subType"].value().toString() ), set, toplevelCommand); + addHistoryCommand( toplevelCommand, false /* !execute */ ); + //properties' visiblility changed: refresh prop. set + propertySetReloaded(true/*preservePrevSelection*/); + d->slotPropertyChanged_primaryKey_enabled = true; + } +} + +void KexiTableDesignerView::slotRowInserted() +{ + updateActions(); + + if (d->addHistoryCommand_in_slotRowInserted_enabled) { + const int row = d->view->currentRow(); + if (row>=0) { + addHistoryCommand( new InsertEmptyRowCommand( this, row ), false /* !execute */ ); + } + } + //TODO? +} + +void KexiTableDesignerView::slotAboutToDeleteRow( + KexiTableItem& item, KexiDB::ResultInfo* result, bool repaint) +{ + Q_UNUSED(result) + Q_UNUSED(repaint) + if (item[COLUMN_ID_ICON].toString()=="key") + d->primaryKeyExists = false; + + if (d->addHistoryCommand_in_slotAboutToDeleteRow_enabled) { + const int row = d->view->data()->findRef(&item); + KoProperty::Set *set = row >=0 ? d->sets->at(row) : 0; + //set can be 0 here, what means "removing empty row" + addHistoryCommand( + new RemoveFieldCommand( this, row, set ), + false /* !execute */ + ); + } +} + +KexiDB::Field * KexiTableDesignerView::buildField( const KoProperty::Set &set ) const +{ + //create a map of property values + kexipluginsdbg << set["type"].value() << endl; + QMap<QCString, QVariant> values = KoProperty::propertyValues(set); + //remove internal values, to avoid creating custom field's properties + QMap<QCString, QVariant>::Iterator it = values.begin(); + KexiDB::Field *field = new KexiDB::Field(); + + while (it!=values.end()) { + const QString propName( it.key() ); + if (d->internalPropertyNames.find(propName.latin1()) || propName.startsWith("this:") + || (/*sanity*/propName=="objectType" && KexiDB::Field::BLOB != KexiDB::intToFieldType( set["type"].value().toInt() ))) + { + QMap<QCString, QVariant>::Iterator it_tmp = it; + ++it; + values.remove(it_tmp); + } + else + ++it; + } + //assign properties to the field + // (note that "objectType" property will be saved as custom property) + if (!KexiDB::setFieldProperties( *field, values )) { + delete field; + return 0; + } + return field; +} + +tristate KexiTableDesignerView::buildSchema(KexiDB::TableSchema &schema, bool beSilent) +{ + if (!d->view->acceptRowEdit()) + return cancelled; + + tristate res = true; + //check for pkey; automatically add a pkey if user wanted + if (!d->primaryKeyExists) { + if (beSilent) { + kexipluginsdbg << "KexiTableDesignerView::buildSchema(): no primay key defined..." << endl; + } + else { + const int questionRes = KMessageBox::questionYesNoCancel(this, + i18n("<p>Table \"%1\" has no <b>primary key</b> defined.</p>" + "<p>Although a primary key is not required, it is needed " + "for creating relations between database tables. " + "Do you want to add primary key automatically now?</p>" + "<p>If you want to add a primary key by hand, press \"Cancel\" " + "to cancel saving table design.</p>").arg(schema.name()), + QString::null, KGuiItem(i18n("&Add Primary Key"), "key"), KStdGuiItem::no(), + "autogeneratePrimaryKeysOnTableDesignSaving"); + if (questionRes==KMessageBox::Cancel) { + return cancelled; + } + else if (questionRes==KMessageBox::Yes) { + //-find unique name, starting with, "id", "id2", .... + int i=0; + int idIndex = 1; //means "id" + QString pkFieldName("id%1"); + QString pkFieldCaption(i18n("Identifier%1", "Id%1")); + while (i<(int)d->sets->size()) { + KoProperty::Set *set = d->sets->at(i); + if (set) { + if ((*set)["name"].value().toString() + == pkFieldName.arg(idIndex==1?QString::null : QString::number(idIndex)) + || (*set)["caption"].value().toString() + == pkFieldCaption.arg(idIndex==1?QString::null : QString::number(idIndex))) + { + //try next id index + i = 0; + idIndex++; + continue; + } + } + i++; + } + pkFieldName = pkFieldName.arg(idIndex==1?QString::null : QString::number(idIndex)); + pkFieldCaption = pkFieldCaption.arg(idIndex==1?QString::null : QString::number(idIndex)); + //ok, add PK with such unique name + d->view->insertEmptyRow(0); + d->view->setCursorPosition(0, COLUMN_ID_CAPTION); + d->view->data()->updateRowEditBuffer(d->view->selectedItem(), COLUMN_ID_CAPTION, + QVariant(pkFieldCaption)); + d->view->data()->updateRowEditBuffer(d->view->selectedItem(), COLUMN_ID_TYPE, + QVariant(KexiDB::Field::IntegerGroup-1/*counting from 0*/)); + if (!d->view->data()->saveRowChanges(*d->view->selectedItem(), true)) { + return cancelled; + } + slotTogglePrimaryKey(); + } + } + } + + //check for duplicates + KoProperty::Set *b = 0; + bool no_fields = true; + int i; + QDict<char> names(101, false); + char dummy; + for (i=0;i<(int)d->sets->size();i++) { + b = d->sets->at(i); + if (b) { + no_fields = false; + const QString name = (*b)["name"].value().toString(); + if (name.isEmpty()) { + if (beSilent) { + kexipluginswarn << + QString("KexiTableDesignerView::buildSchema(): no field caption entered at row %1...") + .arg(i+1) << endl; + } + else { + d->view->setCursorPosition(i, COLUMN_ID_CAPTION); + d->view->startEditCurrentCell(); + KMessageBox::information(this, i18n("You should enter field caption.") ); + } + res = cancelled; + break; + } + if (names[name]) { + break; + } + names.insert( name, &dummy ); //remember + } + } + if (res == true && no_fields) {//no fields added + if (beSilent) { + kexipluginswarn << + "KexiTableDesignerView::buildSchema(): no field defined..." << endl; + } + else { + KMessageBox::sorry(this, + i18n("You have added no fields.\nEvery table should have at least one field.") ); + } + res = cancelled; + } + if (res == true && b && i<(int)d->sets->size()) {//found a duplicate + if (beSilent) { + kexipluginswarn << + QString("KexiTableDesignerView::buildSchema(): duplicated field name '%1'") + .arg((*b)["name"].value().toString()) << endl; + } + else { + d->view->setCursorPosition(i, COLUMN_ID_CAPTION); + d->view->startEditCurrentCell(); +//! @todo for "names hidden" mode we won't get this error because user is unable to change names + KMessageBox::sorry(this, + i18n("You have added \"%1\" field name twice.\nField names cannot be repeated. " + "Correct name of the field.") + .arg((*b)["name"].value().toString()) ); + } + res = cancelled; + } + if (res == true) { + //for every field, create KexiDB::Field definition + for (i=0;i<(int)d->sets->size();i++) { + KoProperty::Set *s = d->sets->at(i); + if (!s) + continue; + KexiDB::Field * f = buildField( *s ); + if (!f) + continue; //hmm? + schema.addField(f); + if (!(*s)["rowSource"].value().toString().isEmpty() && !(*s)["rowSourceType"].value().toString().isEmpty()) { + //add lookup column + KexiDB::LookupFieldSchema *lookupFieldSchema = new KexiDB::LookupFieldSchema(); + lookupFieldSchema->rowSource().setTypeByName( (*s)["rowSourceType"].value().toString() ); + lookupFieldSchema->rowSource().setName( (*s)["rowSource"].value().toString() ); + lookupFieldSchema->setBoundColumn( (*s)["boundColumn"].value().toInt() ); +//! @todo this is backward-compatible code for "single visible column" implementation +//! for multiple columns, only the first is displayed, so there is a data loss is GUI is used +//! -- special koproperty editor needed + QValueList<uint> visibleColumns; + const int visibleColumn = (*s)["visibleColumn"].value().toInt(); + if (visibleColumn >= 0) + visibleColumns.append( (uint)visibleColumn ); + lookupFieldSchema->setVisibleColumns( visibleColumns ); +//! @todo support columnWidths(), columnHeadersVisible(), maximumListRows(), limitToList(), displayWidget() + if (!schema.setLookupFieldSchema(f->name(), lookupFieldSchema)) { + kexipluginswarn << + "KexiTableDesignerView::buildSchema(): !schema.setLookupFieldSchema()" << endl; + delete lookupFieldSchema; + return false; + } + } + } + } + return res; +} + +//! @internal +//! A recursive function for copying alter table actions from undo/redo commands. +static void copyAlterTableActions(KCommand* command, KexiDB::AlterTableHandler::ActionList &actions) +{ + CommandGroup* cmdGroup = dynamic_cast<CommandGroup*>( command ); + if (cmdGroup) {//command group: flatten it + for (QPtrListIterator<KCommand> it(cmdGroup->commands()); it.current(); ++it) + copyAlterTableActions(it.current(), actions); + return; + } + Command* cmd = dynamic_cast<Command*>( command ); + if (!cmd) { + kexipluginswarn << "KexiTableDesignerView::copyAlterTableActions(): cmd is not of type 'Command'!" << endl; + return; + } + KexiDB::AlterTableHandler::ActionBase* action = cmd->createAction(); + //some commands can contain null actions, e.g. "set visibility" command + if (action) + actions.append( action ); +} + +tristate KexiTableDesignerView::buildAlterTableActions(KexiDB::AlterTableHandler::ActionList &actions) +{ + actions.clear(); + kexipluginsdbg << "KexiTableDesignerView::buildAlterTableActions(): " << d->history->commands().count() + << " top-level command(s) to process..." << endl; + for (QPtrListIterator<KCommand> it(d->history->commands()); it.current(); ++it) { + copyAlterTableActions(it.current(), actions); + } + return true; +} + +KexiDB::SchemaData* KexiTableDesignerView::storeNewData(const KexiDB::SchemaData& sdata, bool &cancel) +{ + if (tempData()->table || m_dialog->schemaData()) //must not be + return 0; + + //create table schema definition + tempData()->table = new KexiDB::TableSchema(sdata.name()); + tempData()->table->setName( sdata.name() ); + tempData()->table->setCaption( sdata.caption() ); + tempData()->table->setDescription( sdata.description() ); + + tristate res = buildSchema(*tempData()->table); + cancel = ~res; + + //FINALLY: create table: + if (res == true) { + //todo + KexiDB::Connection *conn = mainWin()->project()->dbConnection(); + res = conn->createTable(tempData()->table); + if (res!=true) + parentDialog()->setStatus(conn, ""); + } + + if (res == true) { + //we've current schema + tempData()->tableSchemaChangedInPreviousView = true; +//not needed; KexiProject emits newItemStored signal //let project know the table is created +// mainWin()->project()->emitTableCreated(*tempData()->table); + } + else { + delete tempData()->table; + tempData()->table = 0; + } + return tempData()->table; +} + +tristate KexiTableDesignerView::storeData(bool dontAsk) +{ + if (!tempData()->table || !m_dialog->schemaData()) { + d->recentResultOfStoreData = false; + return false; + } + + KexiDB::Connection *conn = mainWin()->project()->dbConnection(); + KexiDB::AlterTableHandler *alterTableHandler = 0; + KexiDB::TableSchema *newTable = 0; + + //- create action list for the alter table handler + KexiDB::AlterTableHandler::ActionList actions; + tristate res = buildAlterTableActions( actions ); + bool realAlterTableCanBeUsed = false; //!< @todo this is temporary flag before we switch entirely to real alter table + if (res == true) { + alterTableHandler = new KexiDB::AlterTableHandler( *conn ); + alterTableHandler->setActions(actions); + + if (!d->tempStoreDataUsingRealAlterTable) { + //only compute requirements + KexiDB::AlterTableHandler::ExecutionArguments args; + args.onlyComputeRequirements = true; + (void)alterTableHandler->execute(tempData()->table->name(), args); + res = args.result; + if (res == true && 0 == (args.requirements & (0xffff ^ KexiDB::AlterTableHandler::SchemaAlteringRequired))) + realAlterTableCanBeUsed = true; + } + } + + if (res == true) { + res = KexiTablePart::askForClosingObjectsUsingTableSchema( + this, *conn, *tempData()->table, + i18n("You are about to change the design of table \"%1\" " + "but following objects using this table are opened:") + .arg(tempData()->table->name())); + } + + if (res == true) { + if (!d->tempStoreDataUsingRealAlterTable && !realAlterTableCanBeUsed) { +//! @todo temp; remove this case: + delete alterTableHandler; + alterTableHandler = 0; + // - inform about removing the current table and ask for confirmation + if (!d->dontAskOnStoreData && !dontAsk) { + bool emptyTable; + const QString msg = d->messageForSavingChanges(emptyTable); + if (!emptyTable) { + if (KMessageBox::No == KMessageBox::questionYesNo(this, msg)) + res = cancelled; + } + } + d->dontAskOnStoreData = false; //one-time use + if (~res) { + d->recentResultOfStoreData = res; + return res; + } + // keep old behaviour: + newTable = new KexiDB::TableSchema(); + // copy the schema data + static_cast<KexiDB::SchemaData&>(*newTable) = static_cast<KexiDB::SchemaData&>(*tempData()->table); + res = buildSchema(*newTable); + kexipluginsdbg << "KexiTableDesignerView::storeData() : BUILD SCHEMA:" << endl; + newTable->debug(); + + res = conn->alterTable(*tempData()->table, *newTable); + if (res != true) + parentDialog()->setStatus(conn, ""); + } + else { + KexiDB::AlterTableHandler::ExecutionArguments args; + newTable = alterTableHandler->execute(tempData()->table->name(), args); + res = args.result; + kexipluginsdbg << "KexiTableDesignerView::storeData() : ALTER TABLE EXECUTE: " + << res.toString() << endl; + if (true != res) { + alterTableHandler->debugError(); + parentDialog()->setStatus(alterTableHandler, ""); + } + } + } + if (res == true) { + //change current schema + tempData()->table = newTable; + tempData()->tableSchemaChangedInPreviousView = true; + d->history->clear(); + } + else { + delete newTable; + } + delete alterTableHandler; + d->recentResultOfStoreData = res; + return res; +} + +tristate KexiTableDesignerView::simulateAlterTableExecution(QString *debugTarget) +{ +#ifndef KEXI_NO_UNDOREDO_ALTERTABLE +# ifdef KEXI_DEBUG_GUI + if (mainWin()->activeWindow() != parentDialog()) //to avoid executing for multiple alter table views + return false; + if (!tempData()->table || !m_dialog->schemaData()) + return false; + KexiDB::Connection *conn = mainWin()->project()->dbConnection(); + KexiDB::AlterTableHandler::ActionList actions; + tristate res = buildAlterTableActions( actions ); +//todo: result? + KexiDB::AlterTableHandler alterTableHandler( *conn ); + alterTableHandler.setActions(actions); + KexiDB::AlterTableHandler::ExecutionArguments args; + if (debugTarget) { + args.debugString = debugTarget; + } + else { + args.simulate = true; + } + (void)alterTableHandler.execute(tempData()->table->name(), args); + return args.result; +# else + return false; +# endif +#else + return false; +#endif +} + +void KexiTableDesignerView::slotSimulateAlterTableExecution() +{ + (void)simulateAlterTableExecution(0); +} + +tristate KexiTableDesignerView::executeRealAlterTable() +{ + QSignal signal; + signal.connect( mainWin(), SLOT(slotProjectSave()) ); + d->tempStoreDataUsingRealAlterTable = true; + d->recentResultOfStoreData = false; + signal.activate(); //will call KexiMainWindowImpl::slotProjectSaveAs() and thus storeData() + d->tempStoreDataUsingRealAlterTable = false; + return d->recentResultOfStoreData; +} + +KexiTablePart::TempData* KexiTableDesignerView::tempData() const +{ + return static_cast<KexiTablePart::TempData*>(parentDialog()->tempData()); +} + +/*void KexiTableDesignerView::slotAboutToUpdateRow( + KexiTableItem* item, KexiDB::RowEditBuffer* buffer, KexiDB::ResultInfo* result) +{ + KexiDB::RowEditBuffer::SimpleMap map = buffer->simpleBuffer(); + buffer->debug(); + + QVariant old_type = item->at(1); + QVariant *buf_type = buffer->at( d->view->field(1)->name() ); + + //check if there is a type specified +// if ((old_type.isNull() && !buf_type) || (buf_type && buf_type->isNull())) { + //kdDebug() << "err" << endl; + //} +// allow = true; +// m_dirty = m_dirty | result->success; +}*/ + +#ifdef KEXI_DEBUG_GUI +void KexiTableDesignerView::debugCommand( KCommand* command, int nestingLevel ) +{ + if (dynamic_cast<Command*>(command)) + KexiUtils::addAlterTableActionDebug(dynamic_cast<Command*>(command)->debugString(), nestingLevel); + else + KexiUtils::addAlterTableActionDebug(command->name(), nestingLevel); + //show subcommands + if (dynamic_cast<CommandGroup*>(command)) { + for (QPtrListIterator<KCommand> it(dynamic_cast<CommandGroup*>(command)->commands()); it.current(); ++it) { + debugCommand(it.current(), nestingLevel + 1); + } + } +} +#endif + +void KexiTableDesignerView::addHistoryCommand( KCommand* command, bool execute ) +{ +#ifndef KEXI_NO_UNDOREDO_ALTERTABLE +# ifdef KEXI_DEBUG_GUI + debugCommand( command, 0 ); +# endif + d->history->addCommand( command, execute ); + updateUndoRedoActions(); +#endif +} + +void KexiTableDesignerView::updateUndoRedoActions() +{ +#ifndef KEXI_NO_UNDOREDO_ALTERTABLE + setAvailable("edit_undo", d->historyActionCollection->action("edit_undo")->isEnabled()); + setAvailable("edit_redo", d->historyActionCollection->action("edit_redo")->isEnabled()); +#endif +} + +void KexiTableDesignerView::slotUndo() +{ +#ifndef KEXI_NO_UNDOREDO_ALTERTABLE +# ifdef KEXI_DEBUG_GUI + KexiUtils::addAlterTableActionDebug(QString("UNDO:")); +# endif + d->history->undo(); + updateUndoRedoActions(); +#endif +} + +void KexiTableDesignerView::slotRedo() +{ +#ifndef KEXI_NO_UNDOREDO_ALTERTABLE +# ifdef KEXI_DEBUG_GUI + KexiUtils::addAlterTableActionDebug(QString("REDO:")); +# endif + d->history->redo(); + updateUndoRedoActions(); +#endif +} + +void KexiTableDesignerView::slotCommandExecuted(KCommand *command) +{ +#ifdef KEXI_DEBUG_GUI + debugCommand( command, 1 ); +#endif +} + +void KexiTableDesignerView::slotAboutToShowContextMenu() +{ + //update title + if (propertySet()) { + const KoProperty::Set &set = *propertySet(); + QString captionOrName(set["caption"].value().toString()); + if (captionOrName.isEmpty()) + captionOrName = set["name"].value().toString(); +//! @todo show "field" icon + d->contextMenuTitle->setTitle( i18n("Table field \"%1\"").arg(captionOrName) ); + } + else { + d->contextMenuTitle->setTitle( i18n("Empty table row", "Empty Row") ); + } +} + +QString KexiTableDesignerView::debugStringForCurrentTableSchema(tristate& result) +{ + KexiDB::TableSchema tempTable; + //copy schema data + static_cast<KexiDB::SchemaData&>(tempTable) = static_cast<KexiDB::SchemaData&>(*tempData()->table); + result = buildSchema(tempTable, true /*beSilent*/); + if (true!=result) + return QString::null; + return tempTable.debugString(false /*without name*/); +} + +// -- low-level actions used by undo/redo framework + +void KexiTableDesignerView::clearRow(int row, bool addCommand) +{ + if (!d->view->acceptRowEdit()) + return; + KexiTableItem *item = d->view->itemAt(row); + if (!item) + return; + //remove from prop. set + d->sets->remove( row ); + //clear row in table view (just clear value in COLUMN_ID_TYPE column) +// for (int i=0; i < (int)d->view->data()->columnsCount(); i++) { + if (!addCommand) { + d->addHistoryCommand_in_slotRowUpdated_enabled = false; + d->addHistoryCommand_in_slotPropertyChanged_enabled = false; + d->slotBeforeCellChanged_enabled = false; + } + d->view->data()->updateRowEditBuffer(item, COLUMN_ID_TYPE, QVariant()); + if (!addCommand) { + d->addHistoryCommand_in_slotRowUpdated_enabled = true; + d->addHistoryCommand_in_slotPropertyChanged_enabled = true; + d->slotBeforeCellChanged_enabled = true; + } + d->view->data()->saveRowChanges(*item, true); +} + +void KexiTableDesignerView::insertField(int row, const QString& caption, bool addCommand) +{ + insertFieldInternal(row, 0, caption, addCommand); +} + +void KexiTableDesignerView::insertField(int row, KoProperty::Set& set, bool addCommand) +{ + insertFieldInternal(row, &set, QString::null, addCommand); +} + +void KexiTableDesignerView::insertFieldInternal(int row, KoProperty::Set* set, //const KexiDB::Field& field, + const QString& caption, bool addCommand) +{ + if (set && (!set->contains("type") || !set->contains("caption"))) { + kexipluginswarn << "KexiTableDesignerView::insertField(): no 'type' or 'caption' property in set!" << endl; + return; + } + if (!d->view->acceptRowEdit()) + return; + KexiTableItem *item = d->view->itemAt(row); + if (!item) + return; + if (!addCommand) { + d->addHistoryCommand_in_slotRowUpdated_enabled = false; + d->addHistoryCommand_in_slotPropertyChanged_enabled = false; + d->slotBeforeCellChanged_enabled = false; + } + d->view->data()->updateRowEditBuffer(item, COLUMN_ID_CAPTION, + set ? (*set)["caption"].value() : QVariant(caption));//field.caption()); + d->view->data()->updateRowEditBuffer(item, COLUMN_ID_TYPE, + set ? (int)KexiDB::Field::typeGroup( (*set)["type"].value().toInt() )-1/*counting from 0*/ + : (((int)KexiDB::Field::TextGroup)-1)/*default type, counting from 0*/ + ); + d->view->data()->updateRowEditBuffer(item, COLUMN_ID_DESC, + set ? (*set)["description"].value() : QVariant());//field.description()); + if (!addCommand) { + d->slotBeforeCellChanged_enabled = true; + } + //this will create a new property set: + d->view->data()->saveRowChanges(*item); + if (set) { + KoProperty::Set *newSet = d->sets->at(row); + if (newSet) { + *newSet = *set; //deep copy + } + else { + kexipluginswarn << "KexiTableDesignerView::insertField() !newSet, row==" << row << endl; + } + } + if (!addCommand) { + d->addHistoryCommand_in_slotPropertyChanged_enabled = true; + d->addHistoryCommand_in_slotRowUpdated_enabled = true; + } + d->view->updateRow( row ); + propertySetReloaded(true); +} + +void KexiTableDesignerView::insertEmptyRow( int row, bool addCommand ) +{ + if (!addCommand) { + d->addHistoryCommand_in_slotRowInserted_enabled = false; + } + d->view->insertEmptyRow( row ); + if (!addCommand) { + d->addHistoryCommand_in_slotRowInserted_enabled = true; + } +} + +/*void KexiTableDesignerView::deleteRow( int row ) +{ + d->addHistoryCommand_in_slotAboutToDeleteRow_enabled = false; + d->view->deleteItem( d->view->data()->at(row) ); + d->addHistoryCommand_in_slotAboutToDeleteRow_enabled = true; +}*/ + +void KexiTableDesignerView::deleteRow( int row, bool addCommand ) +{ + KexiTableItem *item = d->view->itemAt( row ); + if (!item) + return; + if (!addCommand) { + d->addHistoryCommand_in_slotAboutToDeleteRow_enabled = false; + } + const bool res = d->view->deleteItem(item); + if (!addCommand) { + d->addHistoryCommand_in_slotAboutToDeleteRow_enabled = true; + } + if (!res) + return; +} + +void KexiTableDesignerView::changeFieldPropertyForRow( int row, + const QCString& propertyName, const QVariant& newValue, + KoProperty::Property::ListData* const listData, bool addCommand ) +{ +#ifdef KEXI_DEBUG_GUI + KexiUtils::addAlterTableActionDebug(QString("** changeFieldProperty: \"") + + QString(propertyName) + "\" to \"" + newValue.toString() + "\"", 2/*nestingLevel*/); +#endif + if (!d->view->acceptRowEdit()) + return; + + KoProperty::Set* set = d->sets->at( row ); + if (!set || !set->contains(propertyName)) + return; + KoProperty::Property &property = set->property(propertyName); + if (listData) { + if (listData->keys.isEmpty()) + property.setListData( 0 ); + else + property.setListData( new KoProperty::Property::ListData(*listData) ); + } + if (propertyName != "type") //delayed type update (we need to have subtype set properly) + property.setValue(newValue); + KexiTableItem *item = d->view->itemAt(row); + Q_ASSERT(item); + + if (propertyName == "type") { + // d->addHistoryCommand_in_slotRowUpdated_enabled = false; +// d->addHistoryCommand_in_slotPropertyChanged_enabled = false; + d->slotPropertyChanged_subType_enabled = false; + d->view->data()->updateRowEditBuffer(item, COLUMN_ID_TYPE, + int( KexiDB::Field::typeGroup( newValue.toInt() ) )-1); + d->view->data()->saveRowChanges(*item); + d->addHistoryCommand_in_slotRowUpdated_enabled = true; +// d->addHistoryCommand_in_slotPropertyChanged_enabled = true; + // d->slotPropertyChanged_subType_enabled = true; + property.setValue(newValue); //delayed type update (we needed to have subtype set properly) + } + + if (!addCommand) { + d->addHistoryCommand_in_slotRowUpdated_enabled = false; + d->addHistoryCommand_in_slotPropertyChanged_enabled = false; + d->slotPropertyChanged_subType_enabled = false; + } + //special cases: properties displayed within the data grid: + if (propertyName == "caption") { + if (!addCommand) { + d->slotBeforeCellChanged_enabled = false; + } + d->view->data()->updateRowEditBuffer(item, COLUMN_ID_CAPTION, newValue); + d->view->data()->saveRowChanges(*item); + if (!addCommand) { + d->slotBeforeCellChanged_enabled = true; + } + } + else if (propertyName == "description") { + if (!addCommand) { + d->slotBeforeCellChanged_enabled = false; + } + d->view->data()->updateRowEditBuffer(item, COLUMN_ID_DESC, newValue); + if (!addCommand) { + d->slotBeforeCellChanged_enabled = true; + } + d->view->data()->saveRowChanges(*item); + } + if (!addCommand) { + d->addHistoryCommand_in_slotPropertyChanged_enabled = true; + d->addHistoryCommand_in_slotRowUpdated_enabled = true; + d->slotPropertyChanged_subType_enabled = true; + } + d->view->updateRow( row ); +} + +void KexiTableDesignerView::changeFieldProperty( int fieldUID, + const QCString& propertyName, const QVariant& newValue, + KoProperty::Property::ListData* const listData, bool addCommand ) +{ + //find a property by UID + const int row = d->sets->findRowForPropertyValue("uid", fieldUID); + if (row<0) { + kexipluginswarn << "KexiTableDesignerView::changeFieldProperty(): field with uid="<<fieldUID<<" not found!"<<endl; + return; + } + changeFieldPropertyForRow(row, propertyName, newValue, listData, addCommand); +} + +void KexiTableDesignerView::changePropertyVisibility( + int fieldUID, const QCString& propertyName, bool visible ) +{ +#ifdef KEXI_DEBUG_GUI + KexiUtils::addAlterTableActionDebug(QString("** changePropertyVisibility: \"") + + QString(propertyName) + "\" to \"" + (visible ? "true" : "false") + "\"", 2/*nestingLevel*/); +#endif + if (!d->view->acceptRowEdit()) + return; + + //find a property by name + const int row = d->sets->findRowForPropertyValue("uid", fieldUID); + if (row<0) + return; + KoProperty::Set* set = d->sets->at( row ); + if (!set || !set->contains(propertyName)) + return; + + KoProperty::Property &property = set->property(propertyName); + if (property.isVisible() != visible) { + property.setVisible(visible); + propertySetReloaded(true); + } +} + +void KexiTableDesignerView::propertySetSwitched() +{ + KexiDataTable::propertySetSwitched(); + + //if (parentDialog()!=parentDialog()->mainWin()->currentDialog()) + // return; //this is not the current dialog's view + + static_cast<KexiTablePart*>(parentDialog()->part())->lookupColumnPage() + ->assignPropertySet(propertySet()); +} + +bool KexiTableDesignerView::isPhysicalAlteringNeeded() +{ + //- create action list for the alter table handler + KexiDB::AlterTableHandler::ActionList actions; + tristate res = buildAlterTableActions( actions ); + if (res != true) + return true; + + KexiDB::Connection *conn = mainWin()->project()->dbConnection(); + KexiDB::AlterTableHandler *alterTableHandler = new KexiDB::AlterTableHandler( *conn ); + alterTableHandler->setActions(actions); + + //only compute requirements + KexiDB::AlterTableHandler::ExecutionArguments args; + args.onlyComputeRequirements = true; + (void)alterTableHandler->execute(tempData()->table->name(), args); + res = args.result; + delete alterTableHandler; + if (res == true && 0 == (args.requirements & (0xffff ^ KexiDB::AlterTableHandler::SchemaAlteringRequired))) + return false; + return true; +} + +#include "kexitabledesignerview.moc" diff --git a/kexi/plugins/tables/kexitabledesignerview.h b/kexi/plugins/tables/kexitabledesignerview.h new file mode 100644 index 00000000..773163b6 --- /dev/null +++ b/kexi/plugins/tables/kexitabledesignerview.h @@ -0,0 +1,258 @@ +/* This file is part of the KDE project + Copyright (C) 2004-2007 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 KEXITABLEDESIGNERINTERVIEW_H +#define KEXITABLEDESIGNERINTERVIEW_H + +#include <koproperty/property.h> +#include <kexidb/alter.h> +#include <core/kexitabledesignerinterface.h> + +#include <kexidatatable.h> +#include "kexitablepart.h" + +class KexiTableItem; +class KexiTableDesignerViewPrivate; +class KCommand; +class CommandGroup; + +namespace KoProperty { + class Set; +} + +//! Design view of the Table Designer +/*! Contains a spreadsheet-like space for entering field definitions. + Property editor is provided for altering field definitions. + + The view also supports Undo and Redo operations. + These are connected to a factility creating a list of actions used + by AlterTableHandler to perform required operation of altering the table. + + Altering itself is performed upon design saving (storeData()). + Saving unstored designs just creates a new table. + Saving changes made to empty (not filled with data) table is performed + by physically deleting the previous table schema and recreating it + TODO: this will be not quite when we have db relationships supported. + + Saving changes made to table containing data requires use of the AlterTableHandler + functionality. +*/ +class KexiTableDesignerView : public KexiDataTable, public KexiTableDesignerInterface +{ + Q_OBJECT + + public: + /*! Creates a new alter table dialog. */ + KexiTableDesignerView(KexiMainWindow *win, QWidget *parent); + + virtual ~KexiTableDesignerView(); + + KexiTablePart::TempData* tempData() const; + + /*! Clears field information entered for row. + This is performed by removing values from caption and data type columns. + Used by InsertFieldCommand to undo inserting a new field. */ + virtual void clearRow(int row, bool addCommand = false); + + /*! Inserts a new field with \a caption for \a row. + Property set is also created. */ + virtual void insertField(int row, const QString& caption, bool addCommand = false); + + /*! Inserts a new \a field for \a row. + Property set is also created. \a set will be deeply-copied into the new set. + Used by InsertFieldCommand to insert a new field. */ + virtual void insertField(int row, KoProperty::Set& set, bool addCommand = false); + + /*! Inserts a new empty row at position \a row. + Used by RemoveFieldCommand as a part of undo inserting a new field; + also used by InsertEmptyRowCommand. */ + virtual void insertEmptyRow( int row, bool addCommand = false ); + + /*! Deletes \a row from the table view. Property set is also deleted. + All the subsequent fields are moved up. Used for undoing InsertEmptyRowCommand + and by RemoveFieldCommand to remove a field. */ + virtual void deleteRow( int row, bool addCommand = false ); + + /*! Deletes a field for \a row. Property set is also deleted. + Used by RemoveFieldCommand to remove a field. */ +// virtual void deleteField( int row ); + + /*! Changes property \a propertyName to \a newValue for a field at row \a row. + If \a listData is not NULL and not empty, a deep copy of it is passed to Property::setListData(). + If \a listData \a nlist if not NULL but empty, Property::setListData(0) is called. */ + virtual void changeFieldPropertyForRow( int row, + const QCString& propertyName, const QVariant& newValue, + KoProperty::Property::ListData* const listData, bool addCommand ); + + /*! Changes property \a propertyName to \a newValue. + Works exactly like changeFieldPropertyForRow() except the field is pointed by \a fieldUID. + Used by ChangeFieldPropertyCommand to change field's property. */ + void changeFieldProperty( int fieldUID, const QCString& propertyName, + const QVariant& newValue, KoProperty::Property::ListData* const listData = 0, + bool addCommand = false ); + + /*! Changes visibility of property \a propertyName to \a visible for a field pointed by \a fieldUID. + Used by ChangePropertyVisibilityCommand. */ + void changePropertyVisibility( int fieldUID, const QCString& propertyName, bool visible ); + + /*! Builds table field's schema by looking at the \a set. */ + KexiDB::Field * buildField( const KoProperty::Set &set ) const; + + /*! Creates temporary table for the current design and returns debug string for it. */ + virtual QString debugStringForCurrentTableSchema(tristate& result); + + /*! Simulates execution of alter table, and puts debug into \a debugTarget. + A case when debugTarget is not 0 is true for the alter table test suite. */ + virtual tristate simulateAlterTableExecution(QString *debugTarget); + + public slots: + /*! Real execution of the Alter Table. For debugging of the real alter table. + \return true on success, false on failure and cancelled if user has cancelled + execution. */ + virtual tristate executeRealAlterTable(); + + protected slots: + /*! Equivalent to updateActions(false). Called on row insert/delete + in a KexiDataAwarePropertySet. */ + void updateActions(); + + virtual void slotUpdateRowActions(int row); + + void slotAboutToShowContextMenu(); + + //! Called before cell change in tableview. + void slotBeforeCellChanged(KexiTableItem *item, int colnum, + QVariant& newValue, KexiDB::ResultInfo* result); + + //! Called on row change in a tableview. + void slotRowUpdated(KexiTableItem *item); + + //! Called before row inserting in tableview. + void slotRowInserted(); +// void slotAboutToInsertRow(KexiTableItem* item, KexiDB::ResultInfo* result, bool repaint); + + //! Called before row deleting in tableview. + void slotAboutToDeleteRow(KexiTableItem& item, KexiDB::ResultInfo* result, bool repaint); + + /*! Called after any property has been changed in the current property set, + to perform some actions (like updating other dependent properties) */ + void slotPropertyChanged(KoProperty::Set& set, KoProperty::Property& property); + + /*! Toggles primary key for currently selected field. + Does nothing for empty row. */ + void slotTogglePrimaryKey(); + + /*! Undoes the recently performed action. */ + void slotUndo(); + + /*! Redoes the recently undoed action. */ + void slotRedo(); + + /*! Reaction on command execution from the command history */ + void slotCommandExecuted(KCommand *command); + + /*! Simulates real execution of the Alter Table. For debugging. */ + void slotSimulateAlterTableExecution(); + + protected: + virtual void updateActions(bool activated); + + //! called whenever data should be reloaded (on switching to this view mode) + void initData(); + + /*! Creates a new property set for \a field. + The property set will be asigned to \a row, and owned by this dialog. + If \a newOne is true, the property set will be marked as newly created. + \return newly created property set. */ + KoProperty::Set* createPropertySet( int row, const KexiDB::Field& field, bool newOne = false ); + + virtual tristate beforeSwitchTo(int mode, bool &dontStore); + + virtual tristate afterSwitchFrom(int mode); + + /*! \return property set associated with currently selected row (i.e. field) + or 0 if current row is empty. */ + virtual KoProperty::Set *propertySet(); + +// void removeCurrentPropertySet(); + + /*! Reimplemented from KexiViewBase, because tables creation is more complex. + No table schema altering is required, so just buildSchema() is used to create a new schema. + */ + virtual KexiDB::SchemaData* storeNewData(const KexiDB::SchemaData& sdata, bool &cancel); + + /*! Reimplemented from KexiViewBase, because table storage is more complex. + Table schema altering may be required, so just buildSchema() is used to create a new schema. + */ + virtual tristate storeData(bool dontAsk = false); + + /*! Builds table schema by looking at the current design. Used in storeNewData() + and storeData(). + If \a beSilent is true, no message boxes are used to show questions or warnings. + This is used in the altertable test suite (kexi/tests/altertable). + \return true on successful schema creating, false on failure and cancelled when there + was a problem with user's design (and user has been informed about it). */ + tristate buildSchema(KexiDB::TableSchema &schema, bool beSilent = false); + + /*! Builds action list usable for KexiDB::AlterTableHandler by looking at undo buffer + of commands' history. Used in storeData() */ + tristate buildAlterTableActions(KexiDB::AlterTableHandler::ActionList &actions); + + /*! Helper, used for slotTogglePrimaryKey() and slotPropertyChanged(). + Assigns primary key icon and value for property set \a propertySet, + and deselects it from previous pkey's row. + \a aWasPKey is internal. + If \a commandGroup is not 0, it is used as parent group for storing actions' history. */ + void switchPrimaryKey(KoProperty::Set &propertySet, bool set, bool aWasPKey = false, + CommandGroup* commandGroup = 0); + + //! Gets subtype strings and names for type \a fieldType. + void getSubTypeListData(KexiDB::Field::TypeGroup fieldTypeGroup, + QStringList& stringsList, QStringList& namesList); + + /*! Adds history command \a command to the undo/redo buffer. + If \a execute is true, the command is executed afterwards. */ + void addHistoryCommand( KCommand* command, bool execute ); + + //! Updates undo/redo shared actions availability by looking at command history's action + void updateUndoRedoActions(); + +#ifdef KEXI_DEBUG_GUI + void debugCommand( KCommand* command, int nestingLevel ); +#endif + + /*! Inserts a new \a field for \a row. + Property set is also created. If \a set is not 0 (the default), + it will be copied into the new set. Used by insertField(). */ + void insertFieldInternal(int row, KoProperty::Set* set, const QString& caption, bool addCommand); + + //! Reimplemented to pass the information also to the "Lookup" tab + virtual void propertySetSwitched(); + + /*! \return true if physical altering is needed for the current list of actions. + Used in KexiTableDesignerView::beforeSwitchTo() to avoid warning about removinf + table data if table recreating is not needed. + True is also returned if there is any trouble with getting the answer. */ + bool isPhysicalAlteringNeeded(); + + private: + KexiTableDesignerViewPrivate *d; +}; + +#endif diff --git a/kexi/plugins/tables/kexitabledesignerview_p.cpp b/kexi/plugins/tables/kexitabledesignerview_p.cpp new file mode 100644 index 00000000..56ef997d --- /dev/null +++ b/kexi/plugins/tables/kexitabledesignerview_p.cpp @@ -0,0 +1,294 @@ +/* This file is part of the KDE project + Copyright (C) 2004-2007 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 "kexitabledesignerview_p.h" +#include "kexitabledesignerview.h" + +#include <qlayout.h> +#include <qlabel.h> +#include <qsplitter.h> + +#include <kiconloader.h> +#include <kdebug.h> +#include <klocale.h> +#include <kaction.h> +#include <kpopupmenu.h> +#include <kmessagebox.h> + +#include <koproperty/set.h> + +#include <kexidb/cursor.h> +#include <kexidb/tableschema.h> +#include <kexidb/connection.h> +#include <kexidb/utils.h> +#include <kexidb/roweditbuffer.h> +#include <kexidb/error.h> +#include <kexiutils/identifier.h> +#include <kexiproject.h> +#include <keximainwindow.h> +#include <widget/tableview/kexidataawarepropertyset.h> +#include <widget/kexicustompropertyfactory.h> +#include <kexiutils/utils.h> +#include <kexidialogbase.h> +#include <kexitableview.h> +#include "kexitabledesignercommands.h" + +using namespace KexiTableDesignerCommands; + +//---------------------------------------------- + +CommandHistory::CommandHistory(KActionCollection *actionCollection, bool withMenus) + : KCommandHistory(actionCollection, withMenus) +{ + // We need ALL the commands because we'll collect reuse their + // data before performing alter table, so set that to the maximum, + // as KCommandHistory has default = 50. + setUndoLimit(INT_MAX); + setRedoLimit(INT_MAX); +} + +void CommandHistory::addCommand(KCommand *command, bool execute) +{ + KCommandHistory::addCommand(command, execute); + m_commandsToUndo.append(command); +} + +void CommandHistory::undo() +{ + if (!m_commandsToUndo.isEmpty()) { + KCommand * cmd = m_commandsToUndo.take( m_commandsToUndo.count()-1 ); + m_commandsToRedo.append( cmd ); + } + KCommandHistory::undo(); +} + +void CommandHistory::redo() +{ + if (!m_commandsToRedo.isEmpty()) { + KCommand * cmd = m_commandsToRedo.take( m_commandsToRedo.count()-1 ); + m_commandsToUndo.append( cmd ); + } + KCommandHistory::redo(); +} + +void CommandHistory::clear() { + KCommandHistory::clear(); m_commandsToUndo.clear(); +} + +//---------------------------------------------- + +KexiTableDesignerViewPrivate::KexiTableDesignerViewPrivate(KexiTableDesignerView* aDesignerView) + : designerView(aDesignerView) + , sets(0) + , uniqueIdCounter(0) + , dontAskOnStoreData(false) + , slotTogglePrimaryKeyCalled(false) + , primaryKeyExists(false) + , slotPropertyChanged_primaryKey_enabled(true) + , slotPropertyChanged_subType_enabled(true) + , addHistoryCommand_in_slotPropertyChanged_enabled(true) + , addHistoryCommand_in_slotRowUpdated_enabled(true) + , addHistoryCommand_in_slotAboutToDeleteRow_enabled(true) + , addHistoryCommand_in_slotRowInserted_enabled(true) + , slotBeforeCellChanged_enabled(true) + , tempStoreDataUsingRealAlterTable(false) +{ + historyActionCollection = new KActionCollection((QWidget*)0,""); + history = new CommandHistory(historyActionCollection, true); + + internalPropertyNames.insert("subType",(char*)1); + internalPropertyNames.insert("uid",(char*)1); + internalPropertyNames.insert("newrow",(char*)1); + internalPropertyNames.insert("rowSource",(char*)1); + internalPropertyNames.insert("rowSourceType",(char*)1); + internalPropertyNames.insert("boundColumn",(char*)1); + internalPropertyNames.insert("visibleColumn",(char*)1); +} + +KexiTableDesignerViewPrivate::~KexiTableDesignerViewPrivate() { + delete sets; + delete historyActionCollection; + delete history; +} + +int KexiTableDesignerViewPrivate::generateUniqueId() +{ + return ++uniqueIdCounter; +} + +void KexiTableDesignerViewPrivate::setPropertyValueIfNeeded( + const KoProperty::Set& set, const QCString& propertyName, + const QVariant& newValue, const QVariant& oldValue, CommandGroup* commandGroup, + bool forceAddCommand, bool rememberOldValue, + QStringList* const slist, QStringList* const nlist) +{ + KoProperty::Property& property = set[propertyName]; + + KoProperty::Property::ListData *oldListData = property.listData() ? + new KoProperty::Property::ListData(*property.listData()) : 0; //remember because we'll change list data soon + if (slist && nlist) { + if (slist->isEmpty() || nlist->isEmpty()) { + property.setListData(0); + } + else { + property.setListData(*slist, *nlist); + } + } + if (oldValue.type() == newValue.type() + && (oldValue == newValue || (!oldValue.isValid() && !newValue.isValid())) + && !forceAddCommand) + { + return; + } + + const bool prev_addHistoryCommand_in_slotPropertyChanged_enabled + = addHistoryCommand_in_slotPropertyChanged_enabled; //remember + addHistoryCommand_in_slotPropertyChanged_enabled = false; + if (property.value() != newValue) + property.setValue( newValue, rememberOldValue ); + if (commandGroup) { + commandGroup->addCommand( + new ChangeFieldPropertyCommand( designerView, set, propertyName, oldValue, newValue, + oldListData, property.listData()) ); + } + delete oldListData; + addHistoryCommand_in_slotPropertyChanged_enabled + = prev_addHistoryCommand_in_slotPropertyChanged_enabled; //restore +} + +void KexiTableDesignerViewPrivate::setPropertyValueIfNeeded( + const KoProperty::Set& set, const QCString& propertyName, + const QVariant& newValue, CommandGroup* commandGroup, + bool forceAddCommand, bool rememberOldValue, + QStringList* const slist, QStringList* const nlist) +{ + KoProperty::Property& property = set[propertyName]; + QVariant oldValue( property.value() ); + setPropertyValueIfNeeded( set, propertyName, newValue, property.value(), + commandGroup, forceAddCommand, rememberOldValue, slist, nlist); +} + +void KexiTableDesignerViewPrivate::setVisibilityIfNeeded( const KoProperty::Set& set, KoProperty::Property* prop, + bool visible, bool &changed, CommandGroup *commandGroup ) +{ + if (prop->isVisible() != visible) { + if (commandGroup) { + commandGroup->addCommand( + new ChangePropertyVisibilityCommand( designerView, set, prop->name(), visible ) ); + } + prop->setVisible( visible ); + changed = true; + } +} + +bool KexiTableDesignerViewPrivate::updatePropertiesVisibility(KexiDB::Field::Type fieldType, KoProperty::Set &set, + CommandGroup *commandGroup) +{ + bool changed = false; + KoProperty::Property *prop; + bool visible; + + prop = &set["subType"]; + kexipluginsdbg << "subType=" << prop->value().toInt() << " type=" << set["type"].value().toInt()<< endl; + + //if there is no more than 1 subType name or it's a PK: hide the property + visible = (prop->listData() && prop->listData()->keys.count() > 1 /*disabled || isObjectTypeGroup*/) + && set["primaryKey"].value().toBool()==false; + setVisibilityIfNeeded( set, prop, visible, changed, commandGroup ); + + prop = &set["objectType"]; + const bool isObjectTypeGroup = set["type"].value().toInt() == (int)KexiDB::Field::BLOB; // used only for BLOBs + visible = isObjectTypeGroup; + setVisibilityIfNeeded( set, prop, visible, changed, commandGroup ); + + prop = &set["unsigned"]; + visible = KexiDB::Field::isNumericType(fieldType); + setVisibilityIfNeeded( set, prop, visible, changed, commandGroup ); + + prop = &set["length"]; + visible = (fieldType == KexiDB::Field::Text); + if (prop->isVisible()!=visible) { +// prop->setVisible( visible ); + //update the length when it makes sense + const int lengthToSet = visible ? KexiDB::Field::defaultTextLength() : 0; + setPropertyValueIfNeeded( set, "length", lengthToSet, + commandGroup, false, false /*!rememberOldValue*/ ); +// if (lengthToSet != prop->value().toInt()) +// prop->setValue( lengthToSet, false ); +// changed = true; + } + setVisibilityIfNeeded( set, prop, visible, changed, commandGroup ); +#ifndef KEXI_NO_UNFINISHED + prop = &set["precision"]; + visible = KexiDB::Field::isFPNumericType(fieldType); + setVisibilityIfNeeded( set, prop, visible, changed, commandGroup ); +#endif + prop = &set["visibleDecimalPlaces"]; + visible = KexiDB::supportsVisibleDecimalPlacesProperty(fieldType); + setVisibilityIfNeeded( set, prop, visible, changed, commandGroup ); + + prop = &set["unique"]; + visible = fieldType != KexiDB::Field::BLOB; + setVisibilityIfNeeded( set, prop, visible, changed, commandGroup ); + + prop = &set["indexed"]; + visible = fieldType != KexiDB::Field::BLOB; + setVisibilityIfNeeded( set, prop, visible, changed, commandGroup ); + + prop = &set["allowEmpty"]; + visible = KexiDB::Field::hasEmptyProperty(fieldType); + setVisibilityIfNeeded( set, prop, visible, changed, commandGroup ); + + prop = &set["autoIncrement"]; + visible = KexiDB::Field::isAutoIncrementAllowed(fieldType); + setVisibilityIfNeeded( set, prop, visible, changed, commandGroup ); + +//! @todo remove this when BLOB supports default value +#ifdef KEXI_NO_UNFINISHED + prop = &set["defaultValue"]; + visible = !isObjectTypeGroup; + setVisibilityIfNeeded( set, prop, visible, changed, commandGroup ); +#endif + + return changed; +} + +QString KexiTableDesignerViewPrivate::messageForSavingChanges(bool &emptyTable, bool skipWarning) +{ + KexiDB::Connection *conn = designerView->mainWin()->project()->dbConnection(); + bool ok; + emptyTable = conn->isEmpty( *designerView->tempData()->table, ok ) && ok; + return i18n("Do you want to save the design now?") + + ( (emptyTable || skipWarning) ? QString::null : + (QString("\n\n") + designerView->part()->i18nMessage(":additional message before saving design", + designerView->parentDialog())) ); +} + +void KexiTableDesignerViewPrivate::updateIconForItem(KexiTableItem &item, KoProperty::Set& set) +{ + QVariant icon; + if (!set["rowSource"].value().toString().isEmpty() && !set["rowSourceType"].value().toString().isEmpty()) + icon = "combo"; + //show/hide icon in the table + view->data()->clearRowEditBuffer(); + view->data()->updateRowEditBuffer(&item, COLUMN_ID_ICON, icon); + view->data()->saveRowChanges(item, true); +} + +#include "kexitabledesignerview_p.moc" diff --git a/kexi/plugins/tables/kexitabledesignerview_p.h b/kexi/plugins/tables/kexitabledesignerview_p.h new file mode 100644 index 00000000..f5650e74 --- /dev/null +++ b/kexi/plugins/tables/kexitabledesignerview_p.h @@ -0,0 +1,191 @@ +/* This file is part of the KDE project + 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 KEXIALTERTABLEDIALOG_P_H +#define KEXIALTERTABLEDIALOG_P_H + +#include "kexitabledesignerview.h" +#include <kcommand.h> + +class KexiDataAwarePropertySet; + +//! @internal indices for table columns +#define COLUMN_ID_ICON 0 +#define COLUMN_ID_CAPTION 1 +#define COLUMN_ID_TYPE 2 +#define COLUMN_ID_DESC 3 + +/*! @internal + Command group, reimplemented to get access to commands(). + We need it to iterate through commands so we can perform a set of ALTER TABLE atomic actions. */ +class CommandGroup : public KMacroCommand +{ + public: + CommandGroup( const QString & name ) + : KMacroCommand(name) + {} + virtual ~CommandGroup() {} + const QPtrList<KCommand>& commands() const { return m_commands; } +}; + +/*! @internal + Command history, reimplemented to get access to commands(). + We need it to iterate through commands so we can perform a set of ALTER TABLE atomic actions. */ +class CommandHistory : public KCommandHistory +{ + Q_OBJECT + public: + CommandHistory(KActionCollection *actionCollection, bool withMenus = true); + + const QPtrList<KCommand>& commands() const { return m_commandsToUndo; } + + void addCommand(KCommand *command, bool execute = true); + + void clear(); + + public slots: + virtual void undo(); + virtual void redo(); + + protected: + QPtrList<KCommand> m_commandsToUndo, m_commandsToRedo; +}; + +//---------------------------------------------- + +//! @internal +class KexiTableDesignerViewPrivate +{ + public: + KexiTableDesignerViewPrivate(KexiTableDesignerView* aDesignerView); + ~KexiTableDesignerViewPrivate(); + + int generateUniqueId(); + + /*! @internal + Sets property \a propertyName in property set \a set to \a newValue. + If \a commandGroup is not 0, a new ChangeFieldPropertyCommand object is added there as well. + While setting the new value, addHistoryCommand_in_slotPropertyChanged_enabled is set to false, + so addHistoryCommand() wont be executed in slotPropertyChanged() as an answer to setting + the property. + + If \a forceAddCommand is false (the default) and \a newValue does not differ from curent property value + (set[propertyName].value()), ChangeFieldPropertyCommand command is not added to the \a commandGroup. + Otherwise, command is always added. + + \a rememberOldValue argument is passed to Property::setValue() + + If \a slist and \a nlist if not NULL and not empty, these are passed to Property::setListData(). + If \a slist and \a nlist if not NULL but empty, Property::setListData(0) is called. + + addHistoryCommand_in_slotPropertyChanged_enabled is then set back to the original state. + */ + void setPropertyValueIfNeeded( const KoProperty::Set& set, const QCString& propertyName, + const QVariant& newValue, CommandGroup* commandGroup, + bool forceAddCommand = false, bool rememberOldValue = true, + QStringList* const slist = 0, QStringList* const nlist = 0); + + /*! Like above but allows to specify \a oldValue. */ + void setPropertyValueIfNeeded( + const KoProperty::Set& set, const QCString& propertyName, + const QVariant& newValue, const QVariant& oldValue, CommandGroup* commandGroup, + bool forceAddCommand = false, bool rememberOldValue = true, + QStringList* const slist = 0, QStringList* const nlist = 0); + + /*! @internal + Used in updatePropertiesVisibility(). + Does nothing if visibility should not be changed, i.e. when prop->isVisible()==visible, + otherwise sets changed to true and sets visibility of property \a prop to \a visible. + */ + void setVisibilityIfNeeded( const KoProperty::Set& set, KoProperty::Property* prop, + bool visible, bool &changed, CommandGroup *commandGroup ); + + bool updatePropertiesVisibility(KexiDB::Field::Type fieldType, KoProperty::Set &set, + CommandGroup *commandGroup = 0); + + /*! \return message used to ask user for accepting saving the design. + \a emptyTable is set to true if the table designed contains no rows. + If \a skipWarning is true, no warning about data loss is appended (useful when + only non-physical altering actions will be performed). */ + QString messageForSavingChanges(bool &emptyTable, bool skipWarning = false); + + /*! Updates icon in the first column, depending on property set \a set. + For example, when "rowSource" and "rowSourceType" propertiesa are not empty, + "combo" icon appears. */ + void updateIconForItem(KexiTableItem &item, KoProperty::Set& set); + + KexiTableDesignerView* designerView; + + KexiTableView *view; //!< helper + + KexiTableViewData *data; + + KexiDataAwarePropertySet *sets; + + int row; //!< used to know if a new row is selected in slotCellSelected() + + KToggleAction *action_toggle_pkey; + + KPopupTitle *contextMenuTitle; + + int uniqueIdCounter; + + //! internal + int maxTypeNameTextWidth; + //! Set to true in beforeSwitchTo() to avoid asking again in storeData() + bool dontAskOnStoreData : 1; + + bool slotTogglePrimaryKeyCalled : 1; + + bool primaryKeyExists : 1; + //! Used in slotPropertyChanged() to avoid infinite recursion + bool slotPropertyChanged_primaryKey_enabled : 1; + //! Used in slotPropertyChanged() to avoid infinite recursion + bool slotPropertyChanged_subType_enabled : 1; + //! used in slotPropertyChanged() to disable addHistoryCommand() + bool addHistoryCommand_in_slotPropertyChanged_enabled : 1; + //! used in slotRowUpdated() to disable addHistoryCommand() + bool addHistoryCommand_in_slotRowUpdated_enabled : 1; + //! used in slotAboutToDeleteRow() to disable addHistoryCommand() + bool addHistoryCommand_in_slotAboutToDeleteRow_enabled : 1; + //! used in slotRowInserted() to disable addHistoryCommand() + bool addHistoryCommand_in_slotRowInserted_enabled : 1; + + //! used to disable slotBeforeCellChanged() + bool slotBeforeCellChanged_enabled : 1; + +//! @tood temp; remove this: + //! Temporary flag, used for testingu the Alter Table machinery. Affects storeData() + //! Used in slotExecuteRealAlterTable() to switch on real alter table for a while. + bool tempStoreDataUsingRealAlterTable : 1; + + /*! Set to a recent result of calling \ref tristate KexiTableDesignerView::storeData(bool dontAsk). + Then, it is used in \ref void KexiTableDesignerView::executeRealAlterTable() + to know what return value should be. */ + tristate recentResultOfStoreData; + + KActionCollection* historyActionCollection; + CommandHistory* history; + + //! A cache used in KexiTableDesignerView::buildField() to quickly identify + //! properties internal to the designer + QAsciiDict<char> internalPropertyNames; +}; + +#endif diff --git a/kexi/plugins/tables/kexitablehandler.desktop b/kexi/plugins/tables/kexitablehandler.desktop new file mode 100644 index 00000000..8491b7a3 --- /dev/null +++ b/kexi/plugins/tables/kexitablehandler.desktop @@ -0,0 +1,118 @@ +[Desktop Entry] +Type=Service +ServiceTypes=Kexi/Handler + +GenericName=Tables +GenericName[bg]=Таблици +GenericName[br]=Taolennoù +GenericName[ca]=Taules +GenericName[cs]=Tabulky +GenericName[cy]=Tablau +GenericName[da]=Tabeller +GenericName[de]=Tabellen +GenericName[el]=Πίνακες +GenericName[eo]=Tabeloj +GenericName[es]=Tablas +GenericName[et]=Tabelid +GenericName[eu]=Taulak +GenericName[fa]=جدولها +GenericName[fi]=Taulukot +GenericName[fr]=Tableaux +GenericName[fy]=Tabellen +GenericName[ga]=Táblaí +GenericName[gl]=Táboas +GenericName[he]=טבלאות +GenericName[hi]=तालिका +GenericName[hr]=Tablice +GenericName[hu]=Táblák +GenericName[is]=Töflur +GenericName[it]=Tabelle +GenericName[ja]=テーブル +GenericName[km]=តារាង +GenericName[lt]=Lentelės +GenericName[lv]=Tabulas +GenericName[ms]=Jadual +GenericName[nb]=Tabeller +GenericName[nds]=Tabellen +GenericName[ne]=तालिका +GenericName[nl]=Tabellen +GenericName[nn]=Tabellar +GenericName[pl]=Tabele +GenericName[pt]=Tabelas +GenericName[pt_BR]=Tabelas +GenericName[ru]=Таблицы +GenericName[se]=Tabeallat +GenericName[sk]=Tabuľky +GenericName[sl]=Tabele +GenericName[sr]=Табеле +GenericName[sr@Latn]=Tabele +GenericName[sv]=Tabeller +GenericName[ta]=அட்டவணைகள் +GenericName[tr]=Tablolar +GenericName[uk]=Таблиці +GenericName[uz]=Jadvallar +GenericName[uz@cyrillic]=Жадваллар +GenericName[zh_CN]=表 +GenericName[zh_TW]=表格 +Name=Tables +Name[bg]=Таблици +Name[br]=Taolennoù +Name[ca]=Taules +Name[cs]=Tabulky +Name[cy]=Tablau +Name[da]=Tabeller +Name[de]=Tabellen +Name[el]=Πίνακες +Name[eo]=Tabeloj +Name[es]=Tablas +Name[et]=Tabelid +Name[eu]=Taulak +Name[fa]=جدولها +Name[fi]=Taulukot +Name[fr]=Tableaux +Name[fy]=Tabellen +Name[ga]=Táblaí +Name[gl]=Táboas +Name[he]=טבלאות +Name[hi]=टेबल्स +Name[hr]=Tablice +Name[hu]=Táblák +Name[is]=Töflur +Name[it]=Tabelle +Name[ja]=テーブル +Name[km]=តារាង +Name[lt]=Lentelės +Name[lv]=Tabulas +Name[ms]=Jadual +Name[nb]=Tabeller +Name[nds]=Tabellen +Name[ne]=तालिकाहरू +Name[nl]=Tabellen +Name[nn]=Tabellar +Name[pl]=Tabele +Name[pt]=Tabelas +Name[pt_BR]=Tabelas +Name[ru]=Таблицы +Name[se]=Tabeallat +Name[sk]=Tabuľky +Name[sl]=Tabele +Name[sr]=Табеле +Name[sr@Latn]=Tabele +Name[sv]=Tabeller +Name[ta]=அட்டவணை +Name[tg]=Ҷадвалҳо +Name[tr]=Tablolar +Name[uk]=Таблиці +Name[uz]=Jadvallar +Name[uz@cyrillic]=Жадваллар +Name[wa]=Tåvleas +Name[zh_CN]=表 +Name[zh_TW]=表格 +X-KDE-Library=kexihandler_table +X-KDE-ParentApp=kexi +X-Kexi-PartVersion=2 +X-Kexi-TypeName=table +X-Kexi-TypeMime=kexi/table +X-Kexi-ItemIcon=table +X-Kexi-SupportsDataExport=true +X-Kexi-SupportsPrinting=true diff --git a/kexi/plugins/tables/kexitablepart.cpp b/kexi/plugins/tables/kexitablepart.cpp new file mode 100644 index 00000000..3d09a81e --- /dev/null +++ b/kexi/plugins/tables/kexitablepart.cpp @@ -0,0 +1,313 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Lucijan Busch <lucijan@kde.org> + Copyright (C) 2002, 2003 Joseph Wenninger <jowenn@kde.org> + Copyright (C) 2004 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 "kexitablepart.h" + +#include <kdebug.h> +#include <kgenericfactory.h> +#include <kmessagebox.h> +#include <ktabwidget.h> +#include <kiconloader.h> + +#include "keximainwindow.h" +#include "kexiproject.h" +#include "kexipartinfo.h" +#include "widget/kexidatatable.h" +#include "widget/tableview/kexidatatableview.h" +#include "kexitabledesignerview.h" +#include "kexitabledesigner_dataview.h" +#include "kexilookupcolumnpage.h" + +#include <kexidb/connection.h> +#include <kexidb/cursor.h> +#include <kexidialogbase.h> + +//! @internal +class KexiTablePart::Private +{ + public: + Private() + { + } + ~Private() + { + delete static_cast<KexiLookupColumnPage*>(lookupColumnPage); + } + QGuardedPtr<KexiLookupColumnPage> lookupColumnPage; +}; + +KexiTablePart::KexiTablePart(QObject *parent, const char *name, const QStringList &l) + : KexiPart::Part(parent, name, l) + , d(new Private()) +{ + // REGISTERED ID: + m_registeredPartID = (int)KexiPart::TableObjectType; + + kdDebug() << "KexiTablePart::KexiTablePart()" << endl; + 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.", + "table"); + m_names["instanceCaption"] = i18n("Table"); + m_supportedViewModes = Kexi::DataViewMode | Kexi::DesignViewMode; +//js TODO: also add Kexi::TextViewMode when we'll have SQL ALTER TABLE EDITOR!!! +} + +KexiTablePart::~KexiTablePart() +{ + delete d; +} + +void KexiTablePart::initPartActions() +{ +} + +void KexiTablePart::initInstanceActions() +{ +//moved to main window createSharedAction(Kexi::DataViewMode, i18n("Filter"), "filter", 0, "tablepart_filter"); + + KAction *a = createSharedToggleAction( + Kexi::DesignViewMode, i18n("Primary Key"), "key", 0, "tablepart_toggle_pkey"); +// Kexi::DesignViewMode, i18n("Toggle Primary Key"), "key", 0, "tablepart_toggle_pkey"); + a->setWhatsThis(i18n("Sets or removes primary key for currently selected field.")); +} + +KexiDialogTempData* KexiTablePart::createTempData(KexiDialogBase* dialog) +{ + return new KexiTablePart::TempData(dialog); +} + +KexiViewBase* KexiTablePart::createView(QWidget *parent, KexiDialogBase* dialog, + KexiPart::Item &item, int viewMode, QMap<QString,QString>*) +{ + KexiMainWindow *win = dialog->mainWin(); + if (!win || !win->project() || !win->project()->dbConnection()) + return 0; + + + KexiTablePart::TempData *temp = static_cast<KexiTablePart::TempData*>(dialog->tempData()); + if (!temp->table) { + temp->table = win->project()->dbConnection()->tableSchema(item.name()); + kdDebug() << "KexiTablePart::execute(): schema is " << temp->table << endl; + } + + if (viewMode == Kexi::DesignViewMode) { + KexiTableDesignerView *t = new KexiTableDesignerView(win, parent); + return t; + } + else if (viewMode == Kexi::DataViewMode) { + if(!temp->table) + return 0; //todo: message + //we're not setting table schema here -it will be forced to set + // in KexiTableDesigner_DataView::afterSwitchFrom() + KexiTableDesigner_DataView *t = new KexiTableDesigner_DataView(win, parent); + return t; + } + return 0; +} + +bool KexiTablePart::remove(KexiMainWindow *win, KexiPart::Item &item) +{ + if (!win || !win->project() || !win->project()->dbConnection()) + return false; + + KexiDB::Connection *conn = win->project()->dbConnection(); + KexiDB::TableSchema *sch = conn->tableSchema(item.identifier()); + + if (sch) { + tristate res = KexiTablePart::askForClosingObjectsUsingTableSchema( + win, *conn, *sch, + i18n("You are about to remove table \"%1\" but following objects using this table are opened:") + .arg(sch->name())); + return true == conn->dropTable( sch ); + } + //last chance: just remove item + return conn->removeObject( item.identifier() ); +} + +tristate KexiTablePart::rename(KexiMainWindow *win, KexiPart::Item & item, + const QString& newName) +{ +//TODO: what about objects (queries/forms) that use old name? + KexiDB::Connection *conn = win->project()->dbConnection(); + KexiDB::TableSchema *sch = conn->tableSchema(item.identifier()); + if (!sch) + return false; + return conn->alterTableName(*sch, newName); +} + +KexiDB::SchemaData* +KexiTablePart::loadSchemaData(KexiDialogBase *dlg, const KexiDB::SchemaData& sdata, int viewMode) +{ + Q_UNUSED( viewMode ); + + return dlg->mainWin()->project()->dbConnection()->tableSchema( sdata.name() ); +} + +#if 0 +KexiPart::DataSource * +KexiTablePart::dataSource() +{ + return new KexiTableDataSource(this); +} +#endif + +tristate KexiTablePart::askForClosingObjectsUsingTableSchema(QWidget *parent, KexiDB::Connection& conn, + KexiDB::TableSchema& table, const QString& msg) +{ + QPtrList<KexiDB::Connection::TableSchemaChangeListenerInterface>* listeners + = conn.tableSchemaChangeListeners(table); + if (!listeners || listeners->isEmpty()) + return true; + + QString openedObjectsStr = "<ul>"; + for (QPtrListIterator<KexiDB::Connection::TableSchemaChangeListenerInterface> it(*listeners); + it.current(); ++it) { + openedObjectsStr += QString("<li>%1</li>").arg(it.current()->listenerInfoString); + } + openedObjectsStr += "</ul>"; + int r = KMessageBox::questionYesNo(parent, + "<p>"+msg+"</p><p>"+openedObjectsStr+"</p><p>" + +i18n("Do you want to close all windows for these objects?"), + QString::null, KGuiItem(i18n("Close windows"),"fileclose"), KStdGuiItem::cancel()); + tristate res; + if (r == KMessageBox::Yes) { + //try to close every window + res = conn.closeAllTableSchemaChangeListeners(table); + if (res!=true) //do not expose closing errors twice; just cancel + res = cancelled; + } + else + res = cancelled; + + return res; +} + +QString +KexiTablePart::i18nMessage(const QCString& englishMessage, KexiDialogBase* dlg) const +{ + if (englishMessage=="Design of object \"%1\" has been modified.") + return i18n("Design of table \"%1\" has been modified."); + + if (englishMessage=="Object \"%1\" already exists.") + return i18n("Table \"%1\" already exists."); + + if (dlg->currentViewMode()==Kexi::DesignViewMode && !dlg->neverSaved() + && englishMessage==":additional message before saving design") + return i18n("Warning! Any data in this table will be removed upon design's saving!"); + + return englishMessage; +} + +void KexiTablePart::setupCustomPropertyPanelTabs(KTabWidget *tab, KexiMainWindow* mainWin) +{ + if (!d->lookupColumnPage) { + d->lookupColumnPage = new KexiLookupColumnPage(0); + connect(d->lookupColumnPage, SIGNAL(jumpToObjectRequested(const QCString&, const QCString&)), + mainWin, SLOT(highlightObject(const QCString&, const QCString&))); + +//! @todo add "Table" tab + + /* + connect(d->dataSourcePage, SIGNAL(formDataSourceChanged(const QCString&, const QCString&)), + KFormDesigner::FormManager::self(), SLOT(setFormDataSource(const QCString&, const QCString&))); + connect(d->dataSourcePage, SIGNAL(dataSourceFieldOrExpressionChanged(const QString&, const QString&, KexiDB::Field::Type)), + KFormDesigner::FormManager::self(), SLOT(setDataSourceFieldOrExpression(const QString&, const QString&, KexiDB::Field::Type))); + connect(d->dataSourcePage, SIGNAL(insertAutoFields(const QString&, const QString&, const QStringList&)), + KFormDesigner::FormManager::self(), SLOT(insertAutoFields(const QString&, const QString&, const QStringList&)));*/ + } + + KexiProject *prj = mainWin->project(); + d->lookupColumnPage->setProject(prj); + +//! @todo add lookup field icon + tab->addTab( d->lookupColumnPage, SmallIconSet("combo"), ""); + tab->setTabToolTip( d->lookupColumnPage, i18n("Lookup column")); +} + +KexiLookupColumnPage* KexiTablePart::lookupColumnPage() const +{ + return d->lookupColumnPage; +} + +//---------------- + +#if 0 +KexiTableDataSource::KexiTableDataSource(KexiPart::Part *part) + : KexiPart::DataSource(part) +{ +} + +KexiTableDataSource::~KexiTableDataSource() +{ +} + +KexiDB::FieldList * +KexiTableDataSource::fields(KexiProject *project, const KexiPart::Item &it) +{ + kdDebug() << "KexiTableDataSource::fields(): " << it.name() << endl; + return project->dbConnection()->tableSchema(it.name()); +} + +KexiDB::Cursor * +KexiTableDataSource::cursor(KexiProject * /*project*/, + const KexiPart::Item &/*it*/, bool /*buffer*/) +{ + return 0; +} +#endif + +//---------------- + +KexiTablePart::TempData::TempData(QObject* parent) + : KexiDialogTempData(parent) + , table(0) + , tableSchemaChangedInPreviousView(true /*to force reloading on startup*/ ) +{ +} + +//---------------- + +/** +TODO +*/ +/* +AboutData( const char *programName, + const char *version, + const char *i18nShortDescription = 0, + int licenseType = License_Unknown, + const char *i18nCopyrightStatement = 0, + const char *i18nText = 0, + const char *homePageAddress = 0, + const char *bugsEmailAddress = "submit@bugs.kde.org" +); + +#define KEXIPART_EXPORT_FACTORY( libname, partClass, aboutData ) \ + static KexiPart::AboutData * libname ## updateAD(KexiPart::AboutData *ad) \ + { ad->setAppName( #libname ); return ad; } \ + K_EXPORT_COMPONENT_FACTORY( libname, KGenericFactory<partClass>(libname ## updateAD(#libname)) ) +*/ + +K_EXPORT_COMPONENT_FACTORY( kexihandler_table, KGenericFactory<KexiTablePart>("kexihandler_table") ) + +#include "kexitablepart.moc" + diff --git a/kexi/plugins/tables/kexitablepart.h b/kexi/plugins/tables/kexitablepart.h new file mode 100644 index 00000000..e4b060ad --- /dev/null +++ b/kexi/plugins/tables/kexitablepart.h @@ -0,0 +1,100 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Lucijan Busch <lucijan@kde.org> + Copyright (C) 2002, 2003 Joseph Wenninger <jowenn@kde.org> + Copyright (C) 2004 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 KEXITABLEPART_H +#define KEXITABLEPART_H + +#include <kexi.h> +#include <kexipart.h> +#include <kexidialogbase.h> +//#include <kexipartdatasource.h> +#include <kexipartitem.h> +#include <kexidb/fieldlist.h> + +class KexiMainWin; +class KexiLookupColumnPage; + +class KexiTablePart : public KexiPart::Part +{ + Q_OBJECT + + public: + KexiTablePart(QObject *parent, const char *name, const QStringList &); + virtual ~KexiTablePart(); + + virtual bool remove(KexiMainWindow *win, KexiPart::Item &item); + + virtual tristate rename(KexiMainWindow *win, KexiPart::Item &item, + const QString& newName); + +// virtual KexiPart::DataSource *dataSource(); + + class TempData : public KexiDialogTempData + { + public: + TempData(QObject* parent); + KexiDB::TableSchema *table; + /*! true, if \a table member has changed in previous view. Used on view switching. + We're checking this flag to see if we should refresh data for DataViewMode. */ + bool tableSchemaChangedInPreviousView : 1; + }; + + static tristate askForClosingObjectsUsingTableSchema( + QWidget *parent, KexiDB::Connection& conn, + KexiDB::TableSchema& table, const QString& msg); + + virtual QString i18nMessage(const QCString& englishMessage, + KexiDialogBase* dlg) const; + + KexiLookupColumnPage* lookupColumnPage() const; + + 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(); + virtual void initInstanceActions(); + + virtual void setupCustomPropertyPanelTabs(KTabWidget *tab, KexiMainWindow* mainWin); + + virtual KexiDB::SchemaData* loadSchemaData(KexiDialogBase *dlg, const KexiDB::SchemaData& sdata, int viewMode); + + private: + class Private; + Private* d; +}; + +#if 0 +class KexiTableDataSource : public KexiPart::DataSource +{ + public: + KexiTableDataSource(KexiPart::Part *part); + ~KexiTableDataSource(); + + virtual KexiDB::FieldList *fields(KexiProject *project, const KexiPart::Item &item); + virtual KexiDB::Cursor *cursor(KexiProject *project, const KexiPart::Item &item, bool buffer); +}; +#endif + +#endif + diff --git a/kexi/plugins/tables/kexitablepartinstui.rc b/kexi/plugins/tables/kexitablepartinstui.rc new file mode 100644 index 00000000..e96a5976 --- /dev/null +++ b/kexi/plugins/tables/kexitablepartinstui.rc @@ -0,0 +1,18 @@ +<!DOCTYPE kpartgui> +<kpartgui name="kexitablepartinst" version="6"> + +<MenuBar> + <Menu name="edit" noMerge="0"> + <text>&Edit</text> + <Separator/> + <Action name="tablepart_toggle_pkey"/> + </Menu> +</MenuBar> + +<ToolBar name="designToolBar" fullWidth="false" noMerge="0"> + <text>Design</text> + <!-- Design View --> + <!-- TODO: reenable after shared toggle actions fix: Action name="tablepart_toggle_pkey"/ --> +</ToolBar> + +</kpartgui> diff --git a/kexi/plugins/tables/kexitablepartui.rc b/kexi/plugins/tables/kexitablepartui.rc new file mode 100644 index 00000000..c78b2587 --- /dev/null +++ b/kexi/plugins/tables/kexitablepartui.rc @@ -0,0 +1,7 @@ +<!DOCTYPE kpartgui> +<kpartgui name="kexitablepart" version="5"> + +<MenuBar> +</MenuBar> + +</kpartgui> |