diff --git a/kexi/core/KexiView.cpp b/kexi/core/KexiView.cpp index c79b5c9553e..30efeb1144e 100644 --- a/kexi/core/KexiView.cpp +++ b/kexi/core/KexiView.cpp @@ -1,772 +1,799 @@ /* This file is part of the KDE project Copyright (C) 2004-2012 Jarosław Staniek 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 "KexiView.h" #include "KexiMainWindowIface.h" #include "KexiWindow.h" #include "kexiproject.h" #include "kexipartinfo.h" #include #include +#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //! @internal Action for toggling view mode class KEXICORE_EXPORT KexiToggleViewModeAction : public KAction { public: //! Creates action for toggling to view mode @a mode. @a slot should have signature //! matching switchedTo(Kexi::ViewMode mode) signal. KexiToggleViewModeAction(Kexi::ViewMode mode, QObject* parent) : KAction( KIcon(Kexi::iconNameForViewMode(mode)), Kexi::nameForViewMode(mode, true/*withAmpersand*/), parent) { setCheckable(true); if (mode == Kexi::DataViewMode) { setObjectName("view_data_mode"); setToolTip(i18n("Switch to data view")); setWhatsThis(i18n("Switches to data view.")); } else if (mode == Kexi::DesignViewMode) { setObjectName("view_design_mode"); setToolTip(i18n("Switch to design view")); setWhatsThis(i18n("Switches to design view.")); } else if (mode == Kexi::TextViewMode) { setObjectName("view_text_mode"); setToolTip(i18n("Switch to text view")); setWhatsThis(i18n("Switches to text view.")); } else { kWarning() << "KexiToggleViewModeAction: invalid mode " << mode; } } }; //------------------------- class KexiView::Private { public: explicit Private(KexiView *qq) : q(qq) , viewWidget(0) , parentView(0) , newlyAssignedID(-1) , viewMode(Kexi::NoViewMode) //unknown! , isDirty(false) , slotSwitchToViewModeInternalEnabled(true) , sortedProperties(false) , recentResultOfSwitchToViewModeInternal(true) , m_mainMenu(0) { } ~Private() { } void toggleViewModeButtonBack(Kexi::ViewMode mode) { QAction *a = toggleViewModeActions.value(mode); if (a) { slotSwitchToViewModeInternalEnabled = false; toggleViewModeActions.value(mode)->blockSignals(true); toggleViewModeButtons.value(mode)->blockSignals(true); toggleViewModeButtons.value(mode)->setChecked(viewMode == mode); toggleViewModeActions.value(mode)->blockSignals(false); toggleViewModeButtons.value(mode)->blockSignals(false); slotSwitchToViewModeInternalEnabled = true; } } KMenu* mainMenu() { if (m_mainMenu) { return m_mainMenu; } if (!window) { return 0; } KexiSmallToolButton* menuButton = new KexiSmallToolButton( KIcon(), window->part()->info()->instanceCaption() + " ", topBarHWidget); menuButton->setToolTip(i18n("Menu for the current window")); menuButton->setWhatsThis(i18n("Shows menu for the current window.")); menuButton->setPopupMode(QToolButton::InstantPopup); topBarLyr->insertWidget(0, menuButton); m_mainMenu = new KMenu; menuButton->setMenu(m_mainMenu); return m_mainMenu; } KoGroupButton *addViewButton(KoGroupButton::GroupPosition pos, Kexi::ViewMode mode, QWidget *parent, const char *slot, const QString &text, QHBoxLayout *btnLyr) { if (!window->supportsViewMode(mode)) { return 0; } KAction *a = new KexiToggleViewModeAction(mode, q); toggleViewModeActions.insert(mode, a); KoGroupButton *btn = new KoGroupButton(pos, parent); toggleViewModeButtons.insert(mode, btn); connect(btn, SIGNAL(toggled(bool)), q, slot); btn->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); btn->setText(text); btn->setIcon(a->icon()); QFont f(KGlobalSettings::toolBarFont()); f.setPixelSize(KexiUtils::smallFont().pixelSize()); btn->setFont(f); btn->setToolTip(a->toolTip()); btn->setWhatsThis(a->whatsThis()); btn->setCheckable(true); btn->setAutoRaise(true); btnLyr->addWidget(btn); return btn; } KexiView *q; QVBoxLayout* mainLyr; QWidget *topBarHWidget; KexiFlowLayout *topBarLyr; QHash toggleViewModeActions; QHash toggleViewModeButtons; KexiSmallToolButton* saveDesignButton; QString defaultIconName; KexiWindow *window; QWidget *viewWidget; KexiView *parentView; QPointer lastFocusedChildBeforeFocusOut; /*! Member set to newly assigned object's ID in storeNewData() and used in storeDataBlock(). This is needed because usually, storeDataBlock() can be called from storeNewData() and in this case window has not yet assigned valid identifier (it has just negative temp. number). \sa KexiWindow::id() */ int newlyAssignedID; /*! Mode for this view. Initialized by KexiWindow::switchToViewMode(). Can be useful when single class is used for more than one view (e.g. KexiDBForm). */ Kexi::ViewMode viewMode; QList children; /*! View-level actions (not shared), owned by the view. */ QList viewActions; QHash viewActionsHash; /*! Main-meny-level actions (not shared), owned by the view. */ QList mainMenuActions; QHash mainMenuActionsHash; bool isDirty; //! Used in slotSwitchToViewModeInternal() to disabling it bool slotSwitchToViewModeInternalEnabled; bool sortedProperties; //! Used in slotSwitchToViewModeInternal() to disabling d->window->switchToViewModeInternal(mode) call. //! Needed because there is another slotSwitchToViewModeInternal() calls if d->window->switchToViewModeInternal(mode) //! did not succeed, so for the second time we block this call. tristate recentResultOfSwitchToViewModeInternal; private: KMenu* m_mainMenu; }; //---------------------------------------------------------- KexiView::KexiView(QWidget *parent) : QWidget(parent) , KexiActionProxy(this) , d(new Private(this)) { QWidget *wi = this; while ((wi = wi->parentWidget()) && !wi->inherits("KexiWindow")) ; d->window = (wi && wi->inherits("KexiWindow")) ? static_cast(wi) : 0; if (d->window) { //init view mode number for this view (obtained from window where this view is created) if (d->window->supportsViewMode(d->window->creatingViewsMode())) d->viewMode = d->window->creatingViewsMode(); } setObjectName( QString("%1_for_%2_object") .arg(Kexi::nameForViewMode(d->viewMode).replace(' ', '_')) .arg(d->window ? d->window->partItem()->name() : QString("??"))); installEventFilter(this); d->mainLyr = new QVBoxLayout(this); d->mainLyr->setContentsMargins(0, 0, 0, 0); if (parentWidget()->inherits("KexiWindow")) { d->topBarHWidget = new QWidget(this); d->topBarHWidget->setFont(KexiUtils::smallFont()); d->mainLyr->addWidget(d->topBarHWidget); QHBoxLayout *topBarHLyr = new QHBoxLayout(d->topBarHWidget); //needed unless KexiFlowLayout properly handles contents margins topBarHLyr->setContentsMargins(0, 0, 0, 0); topBarHLyr->addSpacing(KDialog::marginHint() / 2); d->topBarLyr = new KexiFlowLayout(topBarHLyr, 0, 2); const bool userMode = KexiMainWindowIface::global()->userMode(); if (userMode || d->window->supportedViewModes() == Kexi::DataViewMode || d->window->supportedViewModes() == Kexi::DesignViewMode || d->window->supportedViewModes() == Kexi::TextViewMode) { // nothing to do: only single view mode supported } else { if (parentWidget()->inherits("KexiWindow")) { createViewModeToggleButtons(); } } (void)d->mainMenu(); if (d->viewMode == Kexi::DesignViewMode || d->viewMode == Kexi::TextViewMode) { QAction *a = sharedAction("project_save"); d->saveDesignButton = new KexiSmallToolButton(a, d->topBarHWidget); d->saveDesignButton->setText(i18n("Save")); d->saveDesignButton->setToolTip(i18n("Save current design")); d->saveDesignButton->setWhatsThis(i18n("Saves changes made to the current design.")); d->topBarLyr->addWidget(d->saveDesignButton); a = sharedAction("project_saveas"); d->mainMenu()->addAction(a); } else { d->saveDesignButton = 0; } } else { // no toolbar d->saveDesignButton = 0; d->topBarHWidget = 0; d->topBarLyr = 0; } } KexiView::~KexiView() { delete d; } KexiWindow* KexiView::window() const { return d->window; } bool KexiView::isDirty() const { return d->isDirty; } bool KexiView::isDataEditingInProgress() const { return false; } tristate KexiView::saveDataChanges() { return true; } tristate KexiView::cancelDataChanges() { return true; } Kexi::ViewMode KexiView::viewMode() const { return d->viewMode; } KexiPart::Part* KexiView::part() const { return d->window ? d->window->part() : 0; } tristate KexiView::beforeSwitchTo(Kexi::ViewMode mode, bool & dontStore) { Q_UNUSED(mode); Q_UNUSED(dontStore); return true; } tristate KexiView::afterSwitchFrom(Kexi::ViewMode mode) { Q_UNUSED(mode); return true; } QSize KexiView::preferredSizeHint(const QSize& otherSize) { return otherSize; } void KexiView::closeEvent(QCloseEvent * e) { bool cancel = false; emit closing(&cancel); if (cancel) { e->ignore(); return; } QWidget::closeEvent(e); } KoProperty::Set *KexiView::propertySet() { return 0; } void KexiView::propertySetSwitched() { if (window()) { KexiMainWindowIface::global()->propertySetSwitched(window(), false/*force*/, true/*preservePrevSelection*/, d->sortedProperties); } } void KexiView::propertySetReloaded(bool preservePrevSelection, const QByteArray& propertyToSelect) { if (window()) KexiMainWindowIface::global()->propertySetSwitched( window(), true, preservePrevSelection, d->sortedProperties, propertyToSelect); } void KexiView::setDirty(bool set) { const bool changed = (d->isDirty != set); d->isDirty = set; d->isDirty = isDirty(); if (d->saveDesignButton) d->saveDesignButton->setEnabled(d->isDirty); if (d->parentView) { d->parentView->setDirty(d->isDirty); } else { if (changed && d->window) d->window->dirtyChanged(this); } } void KexiView::setDirty() { setDirty(true); } KexiDB::SchemaData* KexiView::storeNewData(const KexiDB::SchemaData& sdata, KexiView::StoreNewDataOptions options, bool &cancel) { Q_UNUSED(options) Q_UNUSED(cancel) QScopedPointer new_schema(new KexiDB::SchemaData); *new_schema = sdata; KexiDB::Connection *conn = KexiMainWindowIface::global()->project()->dbConnection(); if (!conn->storeObjectSchemaData(*new_schema.data(), true) || !conn->removeDataBlock(new_schema->id()) // for sanity || !KexiMainWindowIface::global()->project()->removeUserDataBlock(new_schema->id()) // for sanity ) { return 0; } d->newlyAssignedID = new_schema->id(); return new_schema.take(); } KexiDB::SchemaData* KexiView::copyData(const KexiDB::SchemaData& sdata, KexiView::StoreNewDataOptions options, bool &cancel) { Q_UNUSED(options) Q_UNUSED(cancel) QScopedPointer new_schema(new KexiDB::SchemaData); *new_schema = sdata; KexiDB::Connection *conn = KexiMainWindowIface::global()->project()->dbConnection(); if (!conn->storeObjectSchemaData(*new_schema.data(), true) || !conn->copyDataBlock(d->window->id(), new_schema->id()) || !KexiMainWindowIface::global()->project()->copyUserDataBlock(d->window->id(), new_schema->id()) ) { return 0; } d->newlyAssignedID = new_schema->id(); return new_schema.take(); } tristate KexiView::storeData(bool dontAsk) { Q_UNUSED(dontAsk); if (!d->window || !d->window->schemaData()) return false; if (!KexiMainWindowIface::global()->project()->dbConnection() ->storeObjectSchemaData(*d->window->schemaData(), false /*existing object*/)) { return false; } setDirty(false); return true; } bool KexiView::loadDataBlock(QString &dataString, const QString& dataID, bool canBeEmpty) { if (!d->window) return false; const tristate res = KexiMainWindowIface::global()->project()->dbConnection() ->loadDataBlock(d->window->id(), dataString, dataID); if (canBeEmpty && ~res) { dataString.clear(); return true; } return res == true; } bool KexiView::storeDataBlock(const QString &dataString, const QString &dataID) { if (!d->window) return false; int effectiveID; if (d->newlyAssignedID > 0) {//ID not yet stored within window, but we've got ID here effectiveID = d->newlyAssignedID; d->newlyAssignedID = -1; } else effectiveID = d->window->id(); return effectiveID > 0 && KexiMainWindowIface::global()->project()->dbConnection()->storeDataBlock( effectiveID, dataString, dataID); } bool KexiView::removeDataBlock(const QString& dataID) { if (!d->window) return false; return KexiMainWindowIface::global()->project()->dbConnection() ->removeDataBlock(d->window->id(), dataID); } bool KexiView::eventFilter(QObject *o, QEvent *e) { if (e->type() == QEvent::FocusIn || e->type() == QEvent::FocusOut) { // kDebug() << "this=[" << o->metaObject()->className() // << objectName() << "] o=[" << o->metaObject()->className() << o->objectName() // << "] focusWidget=[" << (qApp->focusWidget() ? qApp->focusWidget()->metaObject()->className() : QString()) // << (qApp->focusWidget() ? qApp->focusWidget()->objectName() : QString()) << "] ev.type=" << e->type(); if (KexiUtils::hasParent(this, o)) { if (e->type() == QEvent::FocusOut && qApp->focusWidget() && !KexiUtils::hasParent(this, qApp->focusWidget())) { //focus out: when currently focused widget is not a parent of this view emit focus(false); } else if (e->type() == QEvent::FocusIn) { emit focus(true); } if (e->type() == QEvent::FocusOut) { // kDebug() << focusWidget()->className() << " " << focusWidget()->name(); // kDebug() << o->className() << " " << o->name(); KexiView *v = KexiUtils::findParent(o); if (v) { while (v->d->parentView) v = v->d->parentView; if (KexiUtils::hasParent(this, static_cast(v->focusWidget()))) v->d->lastFocusedChildBeforeFocusOut = static_cast(v->focusWidget()); } } if (e->type() == QEvent::FocusIn && m_actionProxyParent) { m_actionProxyParent->m_focusedChild = this; } } } return false; } void KexiView::setViewWidget(QWidget* w, bool focusProxy) { if (d->viewWidget == w) return; if (d->viewWidget) { d->viewWidget->removeEventFilter(this); d->mainLyr->removeWidget(d->viewWidget); } d->viewWidget = w; if (d->viewWidget) { d->viewWidget->setParent(this); d->mainLyr->addWidget(d->viewWidget); d->viewWidget->installEventFilter(this); //} if (focusProxy) setFocusProxy(d->viewWidget); //js: ok? } } void KexiView::addChildView(KexiView* childView) { d->children.append(childView); addActionProxyChild(childView); childView->d->parentView = this; childView->installEventFilter(this); } void KexiView::removeView(Kexi::ViewMode mode) { window()->removeView(mode); } +bool KexiView::setWidgetData(KexiDB::Cursor *cursor) +{ + return cursor->open(); +} + +bool KexiView::setData(KexiDB::Cursor *cursor) +{ + bool ok = setWidgetData(cursor); + if (!ok && cursor) { + // A simple workaround needed because the cursor will be destroyed before + // the error message is built. Kexi 3 would have cleaner solution. + // See https://bugs.kde.org/show_bug.cgi?id=356888 + QString msg = cursor->errorMsg(); + QString desc; + if (!cursor->serverErrorMsg().isEmpty()) { + if (msg.isEmpty()) { + msg = cursor->serverErrorMsg(); + } else { + desc = cursor->serverErrorMsg(); + } + } + window()->setStatus(msg, desc, cursor->recentSQLString()); + } + return ok; +} + void KexiView::setFocus() { if (!d->lastFocusedChildBeforeFocusOut.isNull()) { // kDebug() << "FOCUS: " << d->lastFocusedChildBeforeFocusOut->className() << " " << d->lastFocusedChildBeforeFocusOut->name(); QWidget *w = d->lastFocusedChildBeforeFocusOut; d->lastFocusedChildBeforeFocusOut = 0; w->setFocus(); } else { if (hasFocus()) setFocusInternal(); else setFocusInternal(); } KexiMainWindowIface::global()->invalidateSharedActions(this); } QAction* KexiView::sharedAction(const QString& action_name) { if (part()) { KActionCollection *ac; if ((ac = part()->actionCollectionForMode(viewMode()))) { QAction* a = ac->action(action_name); if (a) return a; } } return KexiActionProxy::sharedAction(action_name); } void KexiView::setAvailable(const QString& action_name, bool set) { if (part()) { KActionCollection *ac; QAction* a; if ((ac = part()->actionCollectionForMode(viewMode())) && (a = ac->action(action_name))) { a->setEnabled(set); } } KexiActionProxy::setAvailable(action_name, set); } void KexiView::updateActions(bool activated) { //do nothing here //do the same for children :) foreach(KexiView* view, d->children) { view->updateActions(activated); } } void KexiView::setViewActions(const QList& actions) { d->viewActions = actions; d->viewActionsHash.clear(); foreach(QAction* action, d->viewActions) { d->viewActionsHash.insert(action->objectName().toLatin1(), action); } } void KexiView::setMainMenuActions(const QList& actions) { d->mainMenuActions = actions; d->mainMenuActionsHash.clear(); foreach(QAction* action, d->mainMenuActions) { d->mainMenuActionsHash.insert(action->objectName().toLatin1(), action); } } QAction* KexiView::viewAction(const char* name) const { return d->viewActionsHash.value(name); } QList KexiView::viewActions() const { return d->viewActions; } void KexiView::toggleViewModeButtonBack() { d->toggleViewModeButtonBack(Kexi::DataViewMode); d->toggleViewModeButtonBack(Kexi::DesignViewMode); d->toggleViewModeButtonBack(Kexi::TextViewMode); } void KexiView::createViewModeToggleButtons() { d->topBarLyr->addSpacing(KDialog::spacingHint()); QWidget *btnCont = new QWidget(d->topBarHWidget); QHBoxLayout *btnLyr = new QHBoxLayout; btnLyr->setSpacing(0); btnLyr->setContentsMargins(0, 0, 0, 0); btnCont->setLayout(btnLyr); d->topBarLyr->addWidget(btnCont); d->topBarLyr->addSpacing(KDialog::spacingHint()); d->addViewButton(KoGroupButton::GroupLeft, Kexi::DataViewMode, btnCont, SLOT(slotSwitchToDataViewModeInternal(bool)), i18n("Data"), btnLyr); d->addViewButton(d->window->supportsViewMode(Kexi::TextViewMode) ? KoGroupButton::GroupCenter : KoGroupButton::GroupRight, Kexi::DesignViewMode, btnCont, SLOT(slotSwitchToDesignViewModeInternal(bool)), i18n("Design"), btnLyr); KoGroupButton *btn = d->addViewButton(KoGroupButton::GroupRight, Kexi::TextViewMode, btnCont, SLOT(slotSwitchToTextViewModeInternal(bool)), QString(), btnLyr); if (btn) { QString customTextViewModeCaption(d->window->internalPropertyValue("textViewModeCaption").toString()); if (customTextViewModeCaption.isEmpty()) { QAction *a = d->toggleViewModeActions.value(Kexi::TextViewMode); btn->setText(a->text()); } else { btn->setText(customTextViewModeCaption); } } toggleViewModeButtonBack(); } void KexiView::slotSwitchToDataViewModeInternal(bool) { slotSwitchToViewModeInternal(Kexi::DataViewMode); } void KexiView::slotSwitchToDesignViewModeInternal(bool) { slotSwitchToViewModeInternal(Kexi::DesignViewMode); } void KexiView::slotSwitchToTextViewModeInternal(bool) { slotSwitchToViewModeInternal(Kexi::TextViewMode); } void KexiView::slotSwitchToViewModeInternal(Kexi::ViewMode mode) { if (!d->slotSwitchToViewModeInternalEnabled) return; if (d->recentResultOfSwitchToViewModeInternal != true) d->recentResultOfSwitchToViewModeInternal = true; else d->recentResultOfSwitchToViewModeInternal = d->window->switchToViewModeInternal(mode); if (d->viewMode != mode) { //switch back visually KoGroupButton *b = d->toggleViewModeButtons.value(mode); d->slotSwitchToViewModeInternalEnabled = false; b->setChecked(false); d->slotSwitchToViewModeInternalEnabled = true; } } void KexiView::initViewActions() { if (!d->topBarLyr) return; if (!d->viewActions.isEmpty() && d->saveDesignButton) { d->topBarLyr->addWidget(new KexiToolBarSeparator(d->topBarHWidget)); } foreach(QAction* action, d->viewActions) { if (action->isSeparator()) { d->topBarLyr->addWidget(new KexiToolBarSeparator(d->topBarHWidget)); } else { KexiSmallToolButton *btn = new KexiSmallToolButton(action, d->topBarHWidget); btn->setText(action->text()); btn->setToolTip(action->toolTip()); btn->setWhatsThis(action->whatsThis()); if (action->dynamicPropertyNames().contains("iconOnly") && action->property("iconOnly").toBool() ) { btn->setToolButtonStyle(Qt::ToolButtonIconOnly); } d->topBarLyr->addWidget(btn); } } } void KexiView::initMainMenuActions() { if (!d->topBarLyr) return; if (d->mainMenuActions.isEmpty()) { return; } d->mainMenu()->clear(); foreach(QAction* action, d->mainMenuActions) { d->mainMenu()->addAction(action); } } void KexiView::setSortedProperties(bool set) { d->sortedProperties = set; } bool KexiView::saveSettings() { return true; } QString KexiView::defaultIconName() const { return d->defaultIconName; } void KexiView::setDefaultIconName(const QString& iconName) { d->defaultIconName = iconName; } QList KexiView::currentParameters() const { return QList(); } #include "KexiView.moc" diff --git a/kexi/core/KexiView.h b/kexi/core/KexiView.h index 30fee4f682e..830b30f83cc 100644 --- a/kexi/core/KexiView.h +++ b/kexi/core/KexiView.h @@ -1,366 +1,375 @@ /* This file is part of the KDE project Copyright (C) 2004-2012 Jarosław Staniek 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 KEXIVIEW_H #define KEXIVIEW_H #include #include #include #include "kexiactionproxy.h" class KexiWindow; namespace KoProperty { class Set; } namespace KexiDB { +class Cursor; class SchemaData; } //! Base class for single view embeddable in KexiWindow. /*! This class automatically works as a proxy for shared (application-wide) actions. KexiView has 'dirty' flag to indicate that view's data has changed. This flag's state is reused by KexiWindow object that contain the view. KexiView objects can be also nested, using addChildView(): any actions and 'dirty' flag are transmited to parent view in this case. KexiView objects are usually allocated within KexiWindow objects by implementing KexiPart::createView() method. See query or table part code for examples. KexiView object can be also allocated without attaching it KexiWindow, especially within dock window. see KexiMainWindow::initNavigator() to see example how KexiBrowser does this. @todo add some protected access methods */ class KEXICORE_EXPORT KexiView : public QWidget, public KexiActionProxy { Q_OBJECT public: explicit KexiView(QWidget *parent); virtual ~KexiView(); //! \return parent KexiWindow that containing this view, //! or 0 if no window contain this view KexiWindow* window() const; /*! Added for convenience. \return KexiPart object that was used to create this view (with a window) or 0 if this view is not created using KexiPart. \sa window() */ KexiPart::Part* part() const; /*! \return preferred size hint, that can be used to resize the view. It is computed using maximum of (a) \a otherSize and (b) current dock area's size, so the view won't exceed this maximum size. The method is used e.g. in KexiWindow::sizeHint(). If you reimplement this method, do not forget to return value of yoursize.boundedTo( KexiView::preferredSizeHint(otherSize) ). */ virtual QSize preferredSizeHint(const QSize& otherSize); void addChildView(KexiView* childView); void removeView(Kexi::ViewMode mode); /*! True if contents (data) of the view is dirty and need to be saved This may or not be used, depending if changes in the window are saved immediately (e.g. like in datatableview) or saved by hand (by user) (e.g. like in alter-table window). "Dirty" flag is reused by KexiWindow::dirty(). Default implementation just uses internal dirty flag, that is false by default. Reimplement this if you e.g. want reuse other "dirty" flag from internal structures that may be changed. */ virtual bool isDirty() const; /*! @return true if data editing is in progress. This is useful to indicate * to the master window that the view should save the before switching to * other view. This information is used in KexiWindow::switchToViewMode(). * Implement this in view that supports data editing, typically * of mode Kexi::DataViewMode. If you do this, also implement * saveDataChanges() and cancelDataChanges(). * Default implementation just returns false. */ virtual bool isDataEditingInProgress() const; /*! Saves changes that are currently made to the associated data. * Implement this in view that supports data editing, typically * of mode Kexi::DataViewMode. If you do this, also implement * isDataEditingInProgress() and cancelDataChanges(). * This method is used by KexiWindow::switchToViewMode(). * Default implementation just returns true. * @return true on success, false on failure and cancelled if the operation * has been cancelled. */ virtual tristate saveDataChanges(); /*! Cancel changes that are currently made to the associated data. * Implement this in view that supports data editing, typically * of mode Kexi::DataViewMode. If you do this, also implement * isDataEditingInProgress() and saveDataChanges(). * This method is used by KexiWindow::switchToViewMode(). * Default implementation just returns true. * @return true on success, false on failure and cancelled if the operation * has been cancelled. */ virtual tristate cancelDataChanges(); /*! \return the view mode for this view. */ Kexi::ViewMode viewMode() const; /*! Reimplemented from KexiActionProxy. \return shared action with name \a action_name for this view. If there's no such action declared in Kexi Part (part()), global shared action is returned (if exists). */ virtual QAction* sharedAction(const QString& action_name); /*! Enables or disables shared action declared in Kexi Part (part()). If there's no such action, global shared action is enabled or disabled (if exists). */ virtual void setAvailable(const QString& action_name, bool set); enum StoreNewDataOption { OverwriteExistingData = 1 //!< Overwerite existing object in storeNewData() }; QString defaultIconName() const; void setDefaultIconName(const QString& iconName); /*! For KexiQueryView */ virtual QList currentParameters() const; Q_DECLARE_FLAGS(StoreNewDataOptions, StoreNewDataOption) public Q_SLOTS: + /*! Sets data. Only works for db-aware view. */ + bool setData(KexiDB::Cursor *cursor); + virtual void setFocus(); /*! Call this in your view's implementation whenever current property set (returned by propertySet()) is switched to other, so property editor contents need to be completely replaced. */ virtual void propertySetSwitched(); /*! Saves settings for the view. Default implementation does nothing and returns true. Implement this if there are settings to save. */ virtual bool saveSettings(); /*! Sets dirty flag on or off. It the flag changes, dirty(bool) signal is emitted by the parent window (KexiWindow), to inform the world about that. If this view has a parent view, setDirty() is called also on parent view. Always use this function to update 'dirty' flag information. */ void setDirty(bool set); /*! Equal to setDirty(true). */ void setDirty(); Q_SIGNALS: //! emitted when the view is about to close void closing(bool *cancel); void focus(bool in); protected: + /*! Sets data to the widget. Default implementation just calls KexiDB::Cursor::open(). + Used internally by setData(KexiDB::Cursor*). + @return true on success. */ + virtual bool setWidgetData(KexiDB::Cursor *cursor); + virtual bool eventFilter(QObject *o, QEvent *e); /*! called by KexiWindow::switchToViewMode() right before window is switched to new mode By default does nothing. Reimplement this if you need to do something before switching to this view. \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. Set \a dontStore to true (it's false by default) if you want to avoid data storing by storeData() or storeNewData(). */ virtual tristate beforeSwitchTo(Kexi::ViewMode mode, bool &dontStore); /*! called by KexiWindow::switchToViewMode() right after window is switched to new mode By default does nothing. Reimplement this if you need to do something after switching to this view. \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(Kexi::ViewMode mode); virtual void closeEvent(QCloseEvent * e); /*! \return a property set for this view. For reimplementation. By default returns NULL. */ virtual KoProperty::Set *propertySet(); /*! Call this in your view's implementation whenever current property set is changed that few properties are now visible and/or few other are invisible, so property editor operating on this property set should be completely reloaded. If \a preservePrevSelection is true and there was a property set assigned before call, previously selected item will be preselected in the editor (if found). */ void propertySetReloaded(bool preservePrevSelection = false, const QByteArray& propertyToSelect = QByteArray()); /*! Tells this view to create and store data of the new object pointed by \a sdata on the backend. Called by KexiWindow::storeNewData() and KexiWindow::storeDataAs(). Default implementation: - makes a deep copy of \a sdata - stores object schema data \a sdata in 'kexi__objects' internal table using Connection::storeObjectSchemaData(). Reimplement this for your needs. Requirements: - deep copy of \a sdata should be made - schema data should be created at the backend (by calling KexiView::storeNewData(const KexiDB::SchemaData&, KexiView::StoreNewDataOptions,bool&)) or using Connection::storeObjectSchemaData() or more specialized method. For example KexiTableDesignerView uses Connection::createTable(TableSchema) for this (TableSchema inherits SchemaData) to store more information than just schema data. You should use such subclasses if needed. Should return newly created schema data object on success. In this case, do not store schema object yourself (make a deep copy if needed). */ virtual KexiDB::SchemaData* storeNewData(const KexiDB::SchemaData& sdata, KexiView::StoreNewDataOptions options, bool &cancel); /*! Tells this view to fully copy existing object's data pointed by \a sdata on the backend. For example, for database tables it whould copy metadata, copy \a sdata, so the copy will have different name, caption and description, and physically copy the table (possibly on the server side). Called by KexiWindow::storeDataAs(). Default implementation: - makes a deep copy of \a sdata - stores object schema data \a sdata in 'kexi__objects' internal table using Connection::storeObjectSchemaData() - makes a full copy of data and user data. Reimplement this for your needs. Requirements: - deep copy of \a sdata should be made - schema data should be created at the backend (by calling KexiView::copyData(const KexiDB::SchemaData&, KexiView::StoreNewDataOptions,bool&)) or using Connection::storeObjectSchemaData() or more specialized method. For example KexiTableDesignerView uses Connection::createTable(TableSchema) for this (TableSchema inherits SchemaData) to store more information than just schema data. Then it copies data table on the server side. You should use such subclasses if needed. Should return newly created schema data object on success. In this case, do not store schema object yourself (make deep copy if needed). */ virtual KexiDB::SchemaData* copyData(const KexiDB::SchemaData& sdata, KexiView::StoreNewDataOptions options, bool &cancel); /*! Loads large string data \a dataString block (e.g. xml form's representation), indexed with optional \a dataID, from the database backend. If \a canBeEmpty is true and there is no data block for dataID, true is returned and \a dataString is set to null string. The default is false. \return true on success \sa storeDataBlock(). */ bool loadDataBlock(QString &dataString, const QString& dataID = QString(), bool canBeEmpty = false); /*! Tells this view to store data changes on the backend. Called by KexiWindow::storeData(). Default implementation: - stores object schema data \a sdata in 'kexi__objects' internal table using Connection::storeObjectSchemaData(). If \a dontAsk is true, no question dialog will be shown to the user. The default is false. Reimplement this for your needs. Should return true on success, false on failure and cancelled when the task should be cancelled. \sa storeNewData() */ virtual tristate storeData(bool dontAsk = false); /*! Stores (potentially large) string data \a dataString, block (e.g. xml form's representation), at the database backend. Block will be stored in "kexi__objectdata" table pointed by this object's id and an optional \a dataID identifier. If window's id is not available (KexiWindow::id()), then ID that was just created in storeNewData() is used (see description of newlyAssignedID()). If there is already such record in the table, it's simply overwritten. \return true on success */ bool storeDataBlock(const QString &dataString, const QString &dataID = QString()); /*! Removes (potentially large) string data (e.g. xml form's representation), pointed by optional \a dataID, from the database backend. \return true on success. Does not fail if the block doe not exists. Note that if \a dataID is not specified, all data blocks for this view will be removed. \sa storeDataBlock(). */ bool removeDataBlock(const QString& dataID = QString()); void setViewWidget(QWidget* w, bool focusProxy = false); /*! Updates actions (e.g. availability). Reimplement it, if needed (you must call superclass impelmentation at the end!). This implementation does nothing for this view but calls updateActions() for every child-view of this view. called by KexiWindow on window's activation (\a activated is true) or deactivation. */ virtual void updateActions(bool activated); virtual void setFocusInternal() { QWidget::setFocus(); } /*! Allows to react on parent window's detaching. @todo it should be called by KexiWindow::youAreDetached(). Default implementation does nothing. Implement it if you want to perform some appropriate actions. */ virtual void windowDetached() {} /*! Allows to react on parent window's attaching. @todo it should be called by KexiWindow::youAreAttached(). Default implementation does nothing. Implement it if you want to perform some appropriate actions. */ virtual void windowAttached() {} /*! Assigns a list of view-level actions. Used by KexiView ctor. */ void setViewActions(const QList& actions); /*! Assigns a list of main-menu-level actions. Used by KexiView ctor. */ void setMainMenuActions(const QList& actions); /*! @return a list of view-level actions. */ QList viewActions() const; /*! @return view-level action for name @a name or 0 if there is no such action. */ QAction* viewAction(const char* name) const; void initViewActions(); void initMainMenuActions(); void toggleViewModeButtonBack(); //! Sets properties in the Property Editor to be sorted if @a set is true. void setSortedProperties(bool set); private Q_SLOTS: void slotSwitchToViewModeInternal(Kexi::ViewMode mode); void slotSwitchToDataViewModeInternal(bool); void slotSwitchToDesignViewModeInternal(bool); void slotSwitchToTextViewModeInternal(bool); private: void createViewModeToggleButtons(); class Private; Private * const d; friend class KexiWindow; }; Q_DECLARE_OPERATORS_FOR_FLAGS(KexiView::StoreNewDataOptions) #endif diff --git a/kexi/core/kexi.cpp b/kexi/core/kexi.cpp index 5c3c83a0d88..708951b9669 100644 --- a/kexi/core/kexi.cpp +++ b/kexi/core/kexi.cpp @@ -1,402 +1,403 @@ /* This file is part of the KDE project Copyright (C) 2003-2012 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kexi.h" #include "kexicmdlineargs.h" #include "KexiRecentProjects.h" #include "KexiMainWindowIface.h" #include "kexipartmanager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Kexi; //! used for speedup //! @internal class KexiInternal { public: static KexiInternal *_int; KexiInternal() : connset(0) { } ~KexiInternal() { delete connset; } static KexiInternal* self() { static bool created = false; if (!created) { _int = new KexiInternal; created = true; } return _int; } static void destroy() { delete _int; _int = 0; } KexiDBConnectionSet* connset; KexiRecentProjects recentProjects; KexiDBConnectionSet recentConnections; KexiDB::DriverManager driverManager; KexiPart::Manager partManager; }; KexiInternal *KexiInternal::_int = 0; KexiDBConnectionSet& Kexi::connset() { //delayed if (!KexiInternal::self()->connset) { //load stored set data, OK? KexiInternal::self()->connset = new KexiDBConnectionSet(); KexiInternal::self()->connset->load(); } return *KexiInternal::self()->connset; } KexiRecentProjects* Kexi::recentProjects() { return &KexiInternal::self()->recentProjects; } KexiDB::DriverManager& Kexi::driverManager() { return KexiInternal::self()->driverManager; } KexiPart::Manager& Kexi::partManager() { return KexiInternal::self()->partManager; } void Kexi::deleteGlobalObjects() { KexiInternal::self()->destroy(); } //temp bool _tempShowMacros = true; bool& Kexi::tempShowMacros() { #ifndef KEXI_MACROS_SUPPORT _tempShowMacros = false; #endif return _tempShowMacros; } bool _tempShowScripts = true; bool& Kexi::tempShowScripts() { #ifndef KEXI_SCRIPTS_SUPPORT _tempShowScripts = false; #endif return _tempShowScripts; } //-------------------------------------------------------------------------------- QString Kexi::nameForViewMode(ViewMode mode, bool withAmpersand) { if (!withAmpersand) return Kexi::nameForViewMode(mode, true).remove('&'); if (mode == NoViewMode) return i18n("&No View"); else if (mode == DataViewMode) return i18n("&Data View"); else if (mode == DesignViewMode) return i18n("D&esign View"); else if (mode == TextViewMode) return i18n("&Text View"); return i18n("&Unknown"); } //-------------------------------------------------------------------------------- QString Kexi::iconNameForViewMode(ViewMode mode) { const char *const id = (mode == DataViewMode) ? koIconNameCStr("state_data") : (mode == DesignViewMode) ? koIconNameCStr("state_edit") : (mode == TextViewMode) ? koIconNameCStr("state_sql"): 0; return QLatin1String(id); } //-------------------------------------------------------------------------------- ObjectStatus::ObjectStatus() : msgHandler(0) { } ObjectStatus::ObjectStatus(const QString& message, const QString& description) : msgHandler(0) { setStatus(message, description); } ObjectStatus::ObjectStatus(KexiDB::Object* dbObject, const QString& message, const QString& description) : msgHandler(0) { setStatus(dbObject, message, description); } ObjectStatus::~ObjectStatus() { delete msgHandler; } const ObjectStatus& ObjectStatus::status() const { return *this; } bool ObjectStatus::error() const { return !message.isEmpty() || (dynamic_cast((QObject*)dbObj) && dynamic_cast((QObject*)dbObj)->error()); } -void ObjectStatus::setStatus(const QString& message, const QString& description) +void ObjectStatus::setStatus(const QString& message, const QString& description, const QString& errorSql) { this->dbObj = 0; this->message = message; this->description = description; + this->errorSql = errorSql; } void ObjectStatus::setStatus(KexiDB::Object* dbObject, const QString& message, const QString& description) { if (dynamic_cast(dbObject)) { dbObj = dynamic_cast(dbObject); } this->message = message; this->description = description; } void ObjectStatus::setStatus(KexiDB::ResultInfo* result, const QString& message, const QString& description) { if (result) { if (message.isEmpty()) this->message = result->msg; else this->message = message + " " + result->msg; if (description.isEmpty()) this->description = result->desc; else this->description = description + " " + result->desc; } else clearStatus(); } void ObjectStatus::setStatus(KexiDB::Object* dbObject, KexiDB::ResultInfo* result, const QString& message, const QString& description) { if (!dbObject) setStatus(result, message, description); else if (!result) setStatus(dbObject, message, description); else { setStatus(dbObject, message, description); setStatus(result, this->message, this->description); } } void ObjectStatus::clearStatus() { message.clear(); description.clear(); } QString ObjectStatus::singleStatusString() const { if (message.isEmpty() || description.isEmpty()) return message; return message + " " + description; } void ObjectStatus::append(const ObjectStatus& otherStatus) { if (message.isEmpty()) { message = otherStatus.message; description = otherStatus.description; return; } const QString s(otherStatus.singleStatusString()); if (s.isEmpty()) return; if (description.isEmpty()) { description = s; return; } description = description + " " + s; } //! @internal class ObjectStatusMessageHandler : public KexiDB::MessageHandler { public: explicit ObjectStatusMessageHandler(ObjectStatus *status) : KexiDB::MessageHandler() , m_status(status) { } virtual ~ObjectStatusMessageHandler() { } virtual void showErrorMessageInternal(const QString &title, const QString &details = QString()) { m_status->setStatus(title, details); } virtual void showErrorMessageInternal(KexiDB::Object *obj, const QString& msg = QString()) { m_status->setStatus(obj, msg); } ObjectStatus *m_status; }; ObjectStatus::operator KexiDB::MessageHandler*() { if (!msgHandler) msgHandler = new ObjectStatusMessageHandler(this); return msgHandler; } void Kexi::initCmdLineArgs(int argc, char *argv[], const KAboutData& aboutData) { KCmdLineArgs::init(argc, argv, &aboutData); KCmdLineArgs::addCmdLineOptions(kexi_options()); } void KEXI_UNFINISHED_INTERNAL(const QString& feature_name, const QString& extra_text, QString* line1, QString* line2) { if (feature_name.isEmpty()) *line1 = i18n("This function is not available for version %1 of %2 application.", QString(KEXI_VERSION_STRING), QString(KEXI_APP_NAME)); else { QString feature_name_(feature_name); *line1 = i18n( "\"%1\" function is not available for version %2 of %3 application.", feature_name_.remove('&'), QString(KEXI_VERSION_STRING), QString(KEXI_APP_NAME)); } *line2 = extra_text; } void KEXI_UNFINISHED(const QString& feature_name, const QString& extra_text) { QString line1, line2; KEXI_UNFINISHED_INTERNAL(feature_name, extra_text, &line1, &line2); if (!line2.isEmpty()) line2.prepend("\n"); KMessageBox::sorry(0, line1 + line2); } QLabel *KEXI_UNFINISHED_LABEL(const QString& feature_name, const QString& extra_text) { QString line1, line2; KEXI_UNFINISHED_INTERNAL(feature_name, extra_text, &line1, &line2); QLabel *label = new QLabel(QLatin1String("") + line1 + QLatin1String("
") + line2); label->setAlignment(Qt::AlignCenter); label->setWordWrap(true); label->setAutoFillBackground(true); label->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred); return label; } //-------------------------------------------------------------------------------- static bool isSpecialIconTheme() { const QString theme(KIconTheme::current().toLower()); return theme.contains(QLatin1String("breeze")) || theme.contains(QLatin1String("highcontrast")); } QString KexiIconName(const QString &baseName) { if (isSpecialIconTheme()) { //! @todo use prefix based on KIconTheme::current()? return QLatin1String("breeze-") + baseName; } return baseName; } KIcon KexiIcon(const QString &baseName) { return KIcon(KexiIconName(baseName)); } QString Kexi::defaultFileBasedDriverIconName() { if (!isSpecialIconTheme()) { KMimeType::Ptr mimeType(KMimeType::mimeType( KexiDB::defaultFileBasedDriverMimeType())); if (!mimeType.isNull()) { return mimeType->iconName(); } KexiDBWarn << KexiDB::defaultFileBasedDriverMimeType() << "mimetype not installed!"; } return koIconName("breeze-kexi-file-database"); } KIcon Kexi::defaultFileBasedDriverIcon() { return KIcon(defaultFileBasedDriverIconName()); } QString Kexi::serverIconName() { return KexiIconName("network-server-database"); } KIcon Kexi::serverIcon() { return KIcon(serverIconName()); } diff --git a/kexi/core/kexi.h b/kexi/core/kexi.h index 06a0b6ec2eb..160d99fad30 100644 --- a/kexi/core/kexi.h +++ b/kexi/core/kexi.h @@ -1,187 +1,190 @@ /* This file is part of the KDE project Copyright (C) 2003-2012 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KEXI_H #define KEXI_H #include #include #include "kexiprojectdata.h" #include "kexidbconnectionset.h" #include "kexiprojectset.h" #include #include class QLabel; class KAboutData; class KexiRecentProjects; namespace KexiPart { class Manager; } namespace KexiDB { class DriverManager; } namespace Kexi { KEXICORE_EXPORT void initCmdLineArgs(int argc, char *argv[], const KAboutData& aboutData); /*! Modes of view for the dialogs. Used mostly for parts and KexiWindow. */ enum ViewMode { AllViewModes = 0, //!< Usable primarily in KexiPart::initInstanceActions() NoViewMode = 0, //!< In KexiView::afterSwitchFrom() and KexiView::beforeSwitchTo() //!< means that parent dialog of the view has not been defined yet. DataViewMode = 1, DesignViewMode = 2, TextViewMode = 4 //!< Also known as SQL View Mode }; Q_DECLARE_FLAGS(ViewModes, ViewMode) /*! @return i18n'ed name of view mode @a mode. If @a withAmpersand is true, ampersands used for accelerators are included, e.g. "&Data View".*/ KEXICORE_EXPORT QString nameForViewMode(ViewMode mode, bool withAmpersand = false); /*! @return icon name of view mode @a mode. */ KEXICORE_EXPORT QString iconNameForViewMode(ViewMode mode); //! A set of known connections KEXICORE_EXPORT KexiDBConnectionSet& connset(); //! A set available of project information KEXICORE_EXPORT KexiRecentProjects* recentProjects(); //! shared driver manager KEXICORE_EXPORT KexiDB::DriverManager& driverManager(); //! shared part manager KEXICORE_EXPORT KexiPart::Manager& partManager(); //! can be called to delete global objects like driverManager and partManager //! (and thus, all loaded factories/plugins) //! before KLibrary::~KLibrary() do this for us KEXICORE_EXPORT void deleteGlobalObjects(); //some temporary flags //! false by default, flag loaded on main window startup KEXICORE_EXPORT bool& tempShowMacros(); //! false by default, flag loaded on main window startup KEXICORE_EXPORT bool& tempShowScripts(); //! false by default, flag loaded on main window startup KEXICORE_EXPORT bool& tempShowScripts(); /*! Helper class for storing object status. */ class KEXICORE_EXPORT ObjectStatus { public: ObjectStatus(); ObjectStatus(const QString& message, const QString& description); ObjectStatus(KexiDB::Object* dbObject, const QString& message, const QString& description); ~ObjectStatus(); const ObjectStatus& status() const; bool error() const; - void setStatus(const QString& message, const QString& description); + void setStatus(const QString& message, const QString& description, + const QString& errorSql = QString()); //! Note: for safety, \a dbObject needs to be derived from QObject, //! otherwise it won't be assigned void setStatus(KexiDB::Object* dbObject, const QString& message = QString(), const QString& description = QString()); void setStatus(KexiDB::ResultInfo* result, const QString& message = QString(), const QString& description = QString()); void setStatus(KexiDB::Object* dbObject, KexiDB::ResultInfo* result, const QString& message = QString(), const QString& description = QString()); void clearStatus(); QString singleStatusString() const; void append(const ObjectStatus& otherStatus); KexiDB::Object *dbObject() const { return dynamic_cast((QObject*)dbObj); } //! Helper returning pseudo handler that just updates this ObjectStatus object //! by receiving a message operator KexiDB::MessageHandler*(); - QString message, description; + QString message; + QString description; + QString errorSql; //!< Recently executed SQL, overrides information from dbObject() protected: QPointer dbObj; //! This is in fact KexiDB::Object KexiDB::MessageHandler* msgHandler; }; /*! \return icon name for default file-based driver (typically icon for something like "application/x-kexiproject-sqlite"). @see KexiDB::defaultFileBasedDriverMimeType() */ KEXICORE_EXPORT QString defaultFileBasedDriverIconName(); /*! \return icon for default file-based driver (typically icon for something like "application/x-kexiproject-sqlite"). If contains special workaround to properly load mimetype icon according to current theme, at least needed for Breeze. @see KexiDB::defaultFileBasedDriverIconName() */ KEXICORE_EXPORT KIcon defaultFileBasedDriverIcon(); /*! \return icon name for database servers. */ KEXICORE_EXPORT QString serverIconName(); /*! \return icon for database servers. */ KEXICORE_EXPORT KIcon serverIcon(); }//namespace Kexi //! @return icon name as understood by Kexi. Icon theme support is improved this way. KEXICORE_EXPORT QString KexiIconName(const QString &baseName); //! @return icon as understood by Kexi. Icon theme support is improved this way. KEXICORE_EXPORT KIcon KexiIcon(const QString &baseName); Q_DECLARE_OPERATORS_FOR_FLAGS(Kexi::ViewModes) //! Displays information that feature "feature_name" is not availabe in the current application version KEXICORE_EXPORT void KEXI_UNFINISHED( const QString& feature_name, const QString& extra_text = QString()); //! Like KEXI_UNFINISHED but returns new label instance with expected text KEXICORE_EXPORT QLabel *KEXI_UNFINISHED_LABEL( const QString& feature_name, const QString& extra_text = QString()); //! Like above - for use inside KexiActionProxy subclass - reuses feature name from shared action's text #define KEXI_UNFINISHED_SHARED_ACTION(action_name) \ KEXI_UNFINISHED(sharedAction(action_name) ? sharedAction(action_name)->text() : QString()) #endif diff --git a/kexi/core/kexiproject.cpp b/kexi/core/kexiproject.cpp index 18393428c0a..c0a282cf28f 100644 --- a/kexi/core/kexiproject.cpp +++ b/kexi/core/kexiproject.cpp @@ -1,1319 +1,1321 @@ /* This file is part of the KDE project Copyright (C) 2003 Lucijan Busch Copyright (C) 2003-2013 Jarosław Staniek 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kexiproject.h" #include "kexiprojectdata.h" #include "kexipartmanager.h" #include "kexipartitem.h" #include "kexipartinfo.h" #include "kexipart.h" #include "KexiWindow.h" #include "KexiWindowData.h" #include "kexi.h" #include "kexiblobbuffer.h" #include "kexiguimsghandler.h" #include //! @return real part class for @a partClass and @a partMime //! for compatibility with Kexi 1.x static QString realPartClass(const QString &partClass, const QString &partMime) { if (partClass.startsWith(QLatin1String("http://"))) { // for compatibility with Kexi 1.x // part mime was used at the time return QLatin1String("org.kexi-project.") + QString(partMime).remove("kexi/"); } return partClass; } class KexiProject::Private { public: explicit Private(KexiProject *qq) : q(qq) , data(0) , tempPartItemID_Counter(-1) , sqlParser(0) , versionMajor(0) , versionMinor(0) , privateIDCounter(0) , itemsRetrieved(false) { } ~Private() { delete data; data = 0; delete sqlParser; foreach(KexiPart::ItemDict* dict, itemDicts) { qDeleteAll(*dict); dict->clear(); } qDeleteAll(itemDicts); qDeleteAll(unstoredItems); unstoredItems.clear(); } void saveClassId(const QString& partClass, int id) { if (!classIds.contains(partClass) && !classNames.contains(id)) { classIds.insert(partClass, id); classNames.insert(id, partClass); } //! @todo what to do with extra ids for the same part or extra class name for the same ID? } //! @return user name for the current project //! @todo the name is taken from connection but it also can be specified otherwise //! if the same connection data is shared by multiple users. This will be especially //! true for 3-tier architectures. QString userName() const { QString name = connection->data()->userName; return name.isNull() ? "" : name; } bool setNameOrCaption(KexiPart::Item& item, const QString* _newName, const QString* _newCaption) { q->clearError(); if (data->userMode()) { return false; } KexiUtils::WaitCursor wait; QString newName; if (_newName) { newName = _newName->trimmed(); KexiDB::MessageTitle et(q); if (newName.isEmpty()) { q->setError(i18n("Could not set empty name for this object.")); return false; } if (q->itemForClass(item.partClass(), newName) != 0) { q->setError(i18n("Could not use this name. Object with name \"%1\" already exists.", newName)); return false; } } QString newCaption; if (_newCaption) { newCaption = _newCaption->trimmed(); } KexiDB::MessageTitle et(q, i18n("Could not rename object \"%1\".", item.name())); if (!q->checkWritable()) return false; KexiPart::Part *part = q->findPartFor(item); if (!part) return false; KexiDB::TransactionGuard tg(*connection); if (!tg.transaction().active()) { q->setError(connection); return false; } if (_newName) { if (!part->rename(item, newName)) { q->setError(part->lastOperationStatus().message, part->lastOperationStatus().description); return false; } if (!connection->executeSQL("UPDATE kexi__objects SET o_name=" + connection->driver()->valueToSQL(KexiDB::Field::Text, newName) + " WHERE o_id=" + QString::number(item.identifier()))) { q->setError(connection); return false; } } if (_newCaption) { if (!connection->executeSQL("UPDATE kexi__objects SET o_caption=" + connection->driver()->valueToSQL(KexiDB::Field::Text, newCaption) + " WHERE o_id=" + QString::number(item.identifier()))) { q->setError(connection); return false; } } if (!tg.commit()) { q->setError(connection); return false; } QString oldName(item.name()); if (_newName) { item.setName(newName); emit q->itemRenamed(item, oldName); } QString oldCaption(item.caption()); if (_newCaption) { item.setCaption(newCaption); emit q->itemCaptionChanged(item, oldCaption); } return true; } KexiProject *q; QPointer connection; QPointer data; QString error_title; KexiPart::MissingPartsList missingParts; QHash classIds; QHash classNames; //! a cache for item() method, indexed by project part's ids QHash itemDicts; QSet unstoredItems; //! helper for getting unique //! temporary identifiers for unstored items int tempPartItemID_Counter; KexiDB::Parser* sqlParser; int versionMajor; int versionMinor; int privateIDCounter; //!< counter: ID for private "document" like Relations window bool itemsRetrieved; }; //--------------------------- KexiProject::KexiProject(const KexiProjectData& pdata, KexiDB::MessageHandler* handler) : QObject(), Object(handler) , d(new Private(this)) { d->data = new KexiProjectData(pdata); } KexiProject::KexiProject(const KexiProjectData& pdata, KexiDB::MessageHandler* handler, KexiDB::Connection* conn) : QObject(), Object(handler) , d(new Private(this)) { d->data = new KexiProjectData(pdata); if (d->data->connectionData() == d->connection->data()) d->connection = conn; else kWarning() << "passed connection's data (" << conn->data()->serverInfoString() << ") is not compatible with project's conn. data (" << d->data->connectionData()->serverInfoString() << ")"; } KexiProject::~KexiProject() { closeConnection(); delete d; } KexiDB::Connection *KexiProject::dbConnection() const { return d->connection; } KexiProjectData* KexiProject::data() const { return d->data; } int KexiProject::versionMajor() const { return d->versionMajor; } int KexiProject::versionMinor() const { return d->versionMinor; } tristate KexiProject::open(bool *incompatibleWithKexi) { Q_ASSERT(incompatibleWithKexi); return openInternal(incompatibleWithKexi); } tristate KexiProject::open() { return openInternal(0); } tristate KexiProject::openInternal(bool *incompatibleWithKexi) { if (!Kexi::partManager().infoList()) { setError(&Kexi::partManager()); return cancelled; } if (incompatibleWithKexi) *incompatibleWithKexi = false; //kDebug() << d->data->databaseName() << d->data->connectionData()->driverName; KexiDB::MessageTitle et(this, i18n("Could not open project \"%1\".", d->data->databaseName())); if (!d->data->connectionData()->fileName().isEmpty()) { QFileInfo finfo(d->data->connectionData()->fileName()); if (!d->data->isReadOnly() && !finfo.isWritable()) { if (KexiProject::askForOpeningNonWritableFileAsReadOnly(0, finfo)) { d->data->setReadOnly(true); } else { return cancelled; } } } if (!createConnection()) { kWarning() << "!createConnection()"; return false; } bool cancel = false; KexiGUIMessageHandler msgHandler; if (!d->connection->useDatabase(d->data->databaseName(), true, &cancel, &msgHandler)) { if (cancel) { return cancelled; } kWarning() << "!d->connection->useDatabase() " << d->data->databaseName() << " " << d->data->connectionData()->driverName; if (d->connection->errorNum() == ERR_NO_DB_PROPERTY) { // //! @todo this is temporary workaround as we have no import driver for SQLite if (/*supported?*/ !d->data->connectionData()->driverName.startsWith(QLatin1String("sqlite"), Qt::CaseInsensitive)) { // if (incompatibleWithKexi) *incompatibleWithKexi = true; } else { KexiDB::MessageTitle et(this, i18n("Database project %1 does not appear to have been created using Kexi and cannot be opened. " "It is an SQLite file created using other tools.", d->data->infoString())); setError(d->connection); } closeConnection(); return false; } setError(d->connection); closeConnection(); return false; } if (!initProject()) return false; return createInternalStructures(/*insideTransaction*/true); } tristate KexiProject::create(bool forceOverwrite) { KexiDB::MessageTitle et(this, i18n("Could not create project \"%1\".", d->data->databaseName())); if (!createConnection()) return false; if (!checkWritable()) return false; if (d->connection->databaseExists(d->data->databaseName())) { if (!forceOverwrite) return cancelled; if (!d->connection->dropDatabase(d->data->databaseName())) { setError(d->connection); closeConnection(); return false; } //kDebug() << "--- DB '" << d->data->databaseName() << "' dropped ---"; } if (!d->connection->createDatabase(d->data->databaseName())) { setError(d->connection); closeConnection(); return false; } //kDebug() << "--- DB '" << d->data->databaseName() << "' created ---"; // and now: open if (!d->connection->useDatabase(d->data->databaseName())) { kWarning() << "--- DB '" << d->data->databaseName() << "' USE ERROR ---"; setError(d->connection); closeConnection(); return false; } //kDebug() << "--- DB '" << d->data->databaseName() << "' used ---"; // KexiDB::Transaction trans = d->connection->beginTransaction(); if (trans.isNull()) return false; if (!createInternalStructures(/*!insideTransaction*/false)) return false; //add some metadata //! @todo put more props. todo - creator, created date, etc. (also to KexiProjectData) KexiDB::DatabaseProperties &props = d->connection->databaseProperties(); if (!props.setValue("kexiproject_major_ver", d->versionMajor) || !props.setCaption("kexiproject_major_ver", i18n("Project major version")) || !props.setValue("kexiproject_minor_ver", d->versionMinor) || !props.setCaption("kexiproject_minor_ver", i18n("Project minor version")) || !props.setValue("project_caption", d->data->caption()) || !props.setCaption("project_caption", i18n("Project caption")) || !props.setValue("project_desc", d->data->description()) || !props.setCaption("project_desc", i18n("Project description"))) return false; if (trans.active() && !d->connection->commitTransaction(trans)) return false; // if (!Kexi::partManager().infoList()) { setError(&Kexi::partManager()); return cancelled; } return initProject(); } bool KexiProject::createInternalStructures(bool insideTransaction) { KexiDB::TransactionGuard tg; if (insideTransaction) { tg.setTransaction(d->connection->beginTransaction()); if (tg.transaction().isNull()) return false; } //Get information about kexiproject version. //kexiproject version is a version of data layer above kexidb layer. KexiDB::DatabaseProperties &props = d->connection->databaseProperties(); bool ok; int storedMajorVersion = props.value("kexiproject_major_ver").toInt(&ok); if (!ok) storedMajorVersion = 0; int storedMinorVersion = props.value("kexiproject_minor_ver").toInt(&ok); if (!ok) storedMinorVersion = 1; bool containsKexi__blobsTable = d->connection->drv_containsTable("kexi__blobs"); int dummy; bool contains_o_folder_id = containsKexi__blobsTable && true == d->connection->querySingleNumber( "SELECT COUNT(o_folder_id) FROM kexi__blobs", dummy, 0, false/*addLimitTo1*/); bool add_folder_id_column = false; //! @todo what about read-only db access? if (storedMajorVersion <= 0) { d->versionMajor = KEXIPROJECT_VERSION_MAJOR; d->versionMinor = KEXIPROJECT_VERSION_MINOR; //For compatibility for projects created before Kexi 1.0 beta 1: //1. no kexiproject_major_ver and kexiproject_minor_ver -> add them if (!d->connection->isReadOnly()) { if (!props.setValue("kexiproject_major_ver", d->versionMajor) || !props.setCaption("kexiproject_major_ver", i18n("Project major version")) || !props.setValue("kexiproject_minor_ver", d->versionMinor) || !props.setCaption("kexiproject_minor_ver", i18n("Project minor version"))) { return false; } } if (containsKexi__blobsTable) { //! @todo what to do for readonly connections? Should we alter kexi__blobs in memory? if (!d->connection->isReadOnly()) { if (!contains_o_folder_id) { add_folder_id_column = true; } } } } if (storedMajorVersion != d->versionMajor || storedMajorVersion != d->versionMinor) { //! @todo version differs: should we change something? d->versionMajor = storedMajorVersion; d->versionMinor = storedMinorVersion; } KexiDB::InternalTableSchema *t_blobs = new KexiDB::InternalTableSchema("kexi__blobs"); t_blobs->addField(new KexiDB::Field("o_id", KexiDB::Field::Integer, KexiDB::Field::PrimaryKey | KexiDB::Field::AutoInc, KexiDB::Field::Unsigned)) .addField(new KexiDB::Field("o_data", KexiDB::Field::BLOB)) .addField(new KexiDB::Field("o_name", KexiDB::Field::Text)) .addField(new KexiDB::Field("o_caption", KexiDB::Field::Text)) .addField(new KexiDB::Field("o_mime", KexiDB::Field::Text, KexiDB::Field::NotNull)) .addField(new KexiDB::Field("o_folder_id", KexiDB::Field::Integer, 0, KexiDB::Field::Unsigned) //references kexi__gallery_folders.f_id //If null, the BLOB only points to virtual "All" folder //WILL BE USED in Kexi >=2.0 ); //*** create global BLOB container, if not present if (containsKexi__blobsTable) { //! just insert this schema d->connection->insertInternalTable(*t_blobs); if (add_folder_id_column && !d->connection->isReadOnly()) { // 2. "kexi__blobs" table contains no "o_folder_id" column -> add it // (by copying table to avoid data loss) KexiDB::TableSchema *kexi__blobsCopy = new KexiDB::TableSchema(*t_blobs); kexi__blobsCopy->setName("kexi__blobs__copy"); if (!d->connection->drv_createTable(*kexi__blobsCopy)) { delete kexi__blobsCopy; delete t_blobs; return false; } // 2.1 copy data (insert 0's into o_folder_id column) if (!d->connection->executeSQL( QString::fromLatin1("INSERT INTO kexi__blobs (o_data, o_name, o_caption, o_mime, o_folder_id) " "SELECT o_data, o_name, o_caption, o_mime, 0 FROM kexi__blobs")) // 2.2 remove the original kexi__blobs || !d->connection->executeSQL(QString::fromLatin1("DROP TABLE kexi__blobs")) //lowlevel // 2.3 rename the copy back into kexi__blobs || !d->connection->drv_alterTableName(*kexi__blobsCopy, "kexi__blobs") ) { //(no need to drop the copy, ROLLBACK will drop it) delete kexi__blobsCopy; delete t_blobs; return false; } delete kexi__blobsCopy; //not needed - physically renamed to kexi_blobs } } else { if (!d->connection->isReadOnly()) { if (!d->connection->createTable(t_blobs, true/*replaceExisting*/)) { delete t_blobs; return false; } } } //Store default part information. //Information for other parts (forms, reports...) are created on demand in KexiWindow::storeNewData() KexiDB::InternalTableSchema *t_parts = new KexiDB::InternalTableSchema("kexi__parts"); t_parts->addField( new KexiDB::Field("p_id", KexiDB::Field::Integer, KexiDB::Field::PrimaryKey | KexiDB::Field::AutoInc, KexiDB::Field::Unsigned) ) .addField(new KexiDB::Field("p_name", KexiDB::Field::Text)) .addField(new KexiDB::Field("p_mime", KexiDB::Field::Text)) .addField(new KexiDB::Field("p_url", KexiDB::Field::Text)); bool containsKexi__partsTable = d->connection->drv_containsTable("kexi__parts"); bool partsTableOk = true; if (containsKexi__partsTable) { //! just insert this schema d->connection->insertInternalTable(*t_parts); } else { if (!d->connection->isReadOnly()) { partsTableOk = d->connection->createTable(t_parts, true/*replaceExisting*/); QScopedPointer fl(t_parts->subList("p_id", "p_name", "p_mime", "p_url")); #define INSERT_RECORD(id, groupName, name) \ if (partsTableOk) { \ partsTableOk = d->connection->insertRecord(*fl, QVariant(int(KexiPart::id)), \ QVariant(groupName), \ QVariant("kexi/" name), QVariant("org.kexi-project." name)); \ if (partsTableOk) { \ d->saveClassId("org.kexi-project." name, int(KexiPart::id)); \ } \ } INSERT_RECORD(TableObjectType, "Tables", "table") INSERT_RECORD(QueryObjectType, "Queries", "query") INSERT_RECORD(FormObjectType, "Forms", "form") INSERT_RECORD(ReportObjectType, "Reports", "report") INSERT_RECORD(ScriptObjectType, "Scripts", "script") INSERT_RECORD(WebObjectType, "Web pages", "web") INSERT_RECORD(MacroObjectType, "Macros", "macro") #undef INSERT_RECORD } } if (!partsTableOk) { delete t_parts; return false; } // User data storage KexiDB::InternalTableSchema *t_userdata = new KexiDB::InternalTableSchema("kexi__userdata"); t_userdata->addField(new KexiDB::Field("d_user", KexiDB::Field::Text, KexiDB::Field::NotNull)) .addField(new KexiDB::Field("o_id", KexiDB::Field::Integer, KexiDB::Field::NotNull, KexiDB::Field::Unsigned)) .addField(new KexiDB::Field("d_sub_id", KexiDB::Field::Text, KexiDB::Field::NotNull | KexiDB::Field::NotEmpty)) .addField(new KexiDB::Field("d_data", KexiDB::Field::LongText)); bool containsKexi__userdataTable = d->connection->drv_containsTable("kexi__userdata"); if (containsKexi__userdataTable) { d->connection->insertInternalTable(*t_userdata); } else if (!d->connection->isReadOnly()) { if (!d->connection->createTable(t_userdata, true/*replaceExisting*/)) { delete t_userdata; return false; } } if (insideTransaction) { if (tg.transaction().active() && !tg.commit()) return false; } return true; } bool KexiProject::createConnection() { if (d->connection) return true; clearError(); KexiDB::MessageTitle et(this); KexiDB::Driver *driver = Kexi::driverManager().driver(d->data->connectionData()->driverName); if (!driver) { setError(&Kexi::driverManager()); return false; } int connectionOptions = 0; if (d->data->isReadOnly()) connectionOptions |= KexiDB::Driver::ReadOnlyConnection; d->connection = driver->createConnection(*d->data->connectionData(), connectionOptions); if (!d->connection) { kWarning() << driver->errorMsg(); setError(driver); return false; } if (!d->connection->connect()) { setError(d->connection); kWarning() << "error connecting: " << (d->connection ? d->connection->errorMsg() : QString()); closeConnection(); return false; } //re-init BLOB buffer //! @todo won't work for subsequent connection KexiBLOBBuffer::setConnection(d->connection); return true; } bool KexiProject::closeConnection() { if (!d->connection) return true; if (!d->connection->disconnect()) { setError(d->connection); return false; } delete d->connection; //this will also clear connection for BLOB buffer d->connection = 0; return true; } bool KexiProject::initProject() { //kDebug() << "checking project parts..."; if (!checkProject()) { return false; } // !@todo put more props. todo - creator, created date, etc. (also to KexiProjectData) KexiDB::DatabaseProperties &props = d->connection->databaseProperties(); QString str(props.value("project_caption").toString()); if (!str.isEmpty()) d->data->setCaption(str); str = props.value("project_desc").toString(); if (!str.isEmpty()) d->data->setDescription(str); return true; } bool KexiProject::isConnected() { if (d->connection && d->connection->isDatabaseUsed()) return true; return false; } KexiPart::ItemDict* KexiProject::items(KexiPart::Info *i) { //kDebug(); if (!i || !isConnected()) return 0; //trying in cache... KexiPart::ItemDict *dict = d->itemDicts.value(i->partClass()); if (dict) return dict; if (d->itemsRetrieved) return 0; if (!retrieveItems()) return 0; return items(i); // try again } bool KexiProject::retrieveItems() { d->itemsRetrieved = true; KexiDB::Cursor *cursor = d->connection->executeQuery( QLatin1String("SELECT o_id, o_name, o_caption, o_type FROM kexi__objects ORDER BY o_type")); if (!cursor) return 0; int recentPartId = -1000; QString partClass; KexiPart::ItemDict *dict = 0; for (cursor->moveFirst(); !cursor->eof(); cursor->moveNext()) { bool ok; int partId = cursor->value(3).toInt(&ok); if (!ok || partId <= 0) { kWarning() << "object of unknown type" << cursor->value(3) << "id=" << cursor->value(0) << "name=" << cursor->value(1); continue; } if (recentPartId == partId) { if (partClass.isEmpty()) // still the same unknown part id continue; } else { // new part id: create next part items dict if it's id for a known class recentPartId = partId; partClass = classForId( partId ); if (partClass.isEmpty()) continue; dict = new KexiPart::ItemDict(); d->itemDicts.insert(partClass, dict); } int ident = cursor->value(0).toInt(&ok); QString objName(cursor->value(1).toString()); if (ok && (ident > 0) && !d->connection->isInternalTableSchema(objName) && KexiDB::isIdentifier(objName)) { KexiPart::Item *it = new KexiPart::Item(); it->setIdentifier(ident); it->setPartClass(partClass); it->setName(objName); it->setCaption(cursor->value(2).toString()); dict->insert(it->identifier(), it); } // kDebug() << "ITEM ADDED == "<* staticObjectArgs) { clearError(); if (viewMode != Kexi::DataViewMode && data()->userMode()) return 0; KexiDB::MessageTitle et(this); KexiPart::Part *part = findPartFor(item); if (!part) return 0; KexiWindow *window = part->openInstance(parent, item, viewMode, staticObjectArgs); if (!window) { - if (part->lastOperationStatus().error()) + if (part->lastOperationStatus().error()) { + m_sql = part->lastOperationStatus().errorSql; // pass proper SQL error setError(i18n("Opening object \"%1\" failed.", item.name()) + "
" + part->lastOperationStatus().message, part->lastOperationStatus().description); + } return 0; } return window; } KexiWindow* KexiProject::openObject(QWidget* parent, const QString &partClass, const QString& name, Kexi::ViewMode viewMode) { KexiPart::Item *it = itemForClass(partClass, name); return it ? openObject(parent, *it, viewMode) : 0; } bool KexiProject::checkWritable() { if (!d->connection->isReadOnly()) return true; setError(i18n("This project is opened as read only.")); return false; } bool KexiProject::removeObject(KexiPart::Item& item) { clearError(); if (data()->userMode()) return false; KexiDB::MessageTitle et(this); if (!checkWritable()) return false; KexiPart::Part *part = findPartFor(item); if (!part) return false; if (!item.neverSaved() && !part->remove(item)) { //! @todo check for errors return false; } if (!item.neverSaved()) { KexiDB::TransactionGuard tg(*d->connection); if (!tg.transaction().active()) { setError(d->connection); return false; } if (!d->connection->removeObject(item.identifier())) { setError(d->connection); return false; } if (!removeUserDataBlock(item.identifier())) { setError(ERR_DELETE_SERVER_ERROR, i18n("Could not remove object's user data.")); return false; } if (!tg.commit()) { setError(d->connection); return false; } } emit itemRemoved(item); //now: remove this item from cache if (part->info()) { KexiPart::ItemDict *dict = d->itemDicts.value(part->info()->partClass()); if (!(dict && dict->remove(item.identifier()))) d->unstoredItems.remove(&item);//remove temp. } return true; } bool KexiProject::renameObject(KexiPart::Item& item, const QString& newName) { return d->setNameOrCaption(item, &newName, 0); } bool KexiProject::setObjectCaption(KexiPart::Item& item, const QString& newCaption) { return d->setNameOrCaption(item, 0, &newCaption); } KexiPart::Item* KexiProject::createPartItem(KexiPart::Info *info, const QString& suggestedCaption) { clearError(); if (data()->userMode()) return 0; KexiDB::MessageTitle et(this); KexiPart::Part *part = Kexi::partManager().part(info); if (!part) { setError(&Kexi::partManager()); return 0; } KexiPart::ItemDict *dict = items(info); if (!dict) { dict = new KexiPart::ItemDict(); d->itemDicts.insert(info->partClass(), dict); } QSet storedItemNames; foreach(KexiPart::Item* item, *dict) { storedItemNames.insert(item->name()); } QSet unstoredItemNames; foreach(KexiPart::Item* item, d->unstoredItems) { unstoredItemNames.insert(item->name()); } //find new, unique default name for this item int n; QString new_name; QString base_name; if (suggestedCaption.isEmpty()) { n = 1; base_name = part->instanceName(); } else { n = 0; //means: try not to add 'n' base_name = KexiUtils::stringToIdentifier(suggestedCaption).toLower(); } base_name = KexiUtils::stringToIdentifier(base_name).toLower(); do { new_name = base_name; if (n >= 1) new_name += QString::number(n); if (storedItemNames.contains(new_name)) { n++; continue; //stored exists! } if (!unstoredItemNames.contains(new_name)) break; //unstored doesn't exist n++; } while (n < 1000/*sanity*/); if (n >= 1000) return 0; QString new_caption(suggestedCaption.isEmpty() ? part->info()->instanceCaption() : suggestedCaption); if (n >= 1) new_caption += QString::number(n); KexiPart::Item *item = new KexiPart::Item(); item->setIdentifier(--d->tempPartItemID_Counter); //temporary item->setPartClass(info->partClass()); item->setName(new_name); item->setCaption(new_caption); item->setNeverSaved(true); d->unstoredItems.insert(item); return item; } KexiPart::Item* KexiProject::createPartItem(KexiPart::Part *part, const QString& suggestedCaption) { return createPartItem(part->info(), suggestedCaption); } void KexiProject::deleteUnstoredItem(KexiPart::Item *item) { if (!item) return; d->unstoredItems.remove(item); delete item; } KexiDB::Parser* KexiProject::sqlParser() { if (!d->sqlParser) { if (!d->connection) return 0; d->sqlParser = new KexiDB::Parser(d->connection); } return d->sqlParser; } const char warningNoUndo[] = I18N_NOOP("Warning: entire project's data will be removed."); /*static*/ KexiProject* KexiProject::createBlankProject(bool &cancelled, const KexiProjectData& data, KexiDB::MessageHandler* handler) { cancelled = false; KexiProject *prj = new KexiProject(data, handler); tristate res = prj->create(false); if (~res) { //! @todo move to KexiMessageHandler if (KMessageBox::Yes != KMessageBox::warningYesNo(0, "" + i18n( "The project %1 already exists.\n" "Do you want to replace it with a new, blank one?", prj->data()->infoString()) + "\n" + i18n(warningNoUndo) + "", QString(), KGuiItem(i18n("Replace")), KStandardGuiItem::cancel())) //! @todo add serverInfoString() for server-based prj { delete prj; cancelled = true; return 0; } res = prj->create(true/*overwrite*/); } if (res != true) { delete prj; return 0; } //kDebug() << "new project created --- "; //! @todo Kexi::recentProjects().addProjectData( data ); return prj; } /*static*/ tristate KexiProject::dropProject(const KexiProjectData& data, KexiDB::MessageHandler* handler, bool dontAsk) { if (!dontAsk && KMessageBox::Yes != KMessageBox::warningYesNo(0, i18n("Do you want to drop the project \"%1\"?", static_cast(&data)->name()) + "\n" + i18n(warningNoUndo))) return cancelled; KexiProject prj(data, handler); if (!prj.open()) return false; if (prj.dbConnection()->isReadOnly()) { handler->showErrorMessage( i18n("Could not drop this project. Database connection for this project has been opened as read only.")); return false; } return prj.dbConnection()->dropDatabase(); } bool KexiProject::checkProject(const QString& singlePartClass) { clearError(); //! @todo catch errors! if (!d->connection->isDatabaseUsed()) { setError(d->connection); return false; } bool containsKexi__partsTable = d->connection->drv_containsTable("kexi__parts"); if (containsKexi__partsTable) { // check if kexi__parts exists, if missing, createInternalStructures() will create it QString sql("SELECT p_id, p_name, p_mime, p_url FROM kexi__parts ORDER BY p_id"); if (!singlePartClass.isEmpty()) { sql.append(QString(" WHERE p_url=%1").arg(d->connection->driver()->escapeString(singlePartClass))); } KexiDB::Cursor *cursor = d->connection->executeQuery(sql); if (!cursor) { setError(d->connection); return false; } bool saved = false; for (cursor->moveFirst(); !cursor->eof(); cursor->moveNext()) { const QString partMime( cursor->value(2).toString() ); QString partClass( cursor->value(3).toString() ); partClass = realPartClass(partClass, partMime); if (partClass == QLatin1String("uk.co.piggz.report")) { // compatibility partClass = QLatin1String("org.kexi-project.report"); } KexiPart::Info *info = Kexi::partManager().infoForClass(partClass); bool ok; const int classId = cursor->value(0).toInt(&ok); if (!ok || classId <= 0) { kWarning() << "Invalid class Id" << classId << "; part" << partClass << "will not be used"; } if (info && ok && classId > 0) { d->saveClassId(partClass, classId); saved = true; } else { KexiPart::MissingPart m; m.name = cursor->value(1).toString(); m.className = partClass; d->missingParts.append(m); } } d->connection->deleteCursor(cursor); if (!saved && !singlePartClass.isEmpty()) { return false; // failure is single part class was not found } } return true; } int KexiProject::generatePrivateID() { return --d->privateIDCounter; } bool KexiProject::createIdForPart(const KexiPart::Info& info) { int p_id = idForClass(info.partClass()); if (p_id > 0) { return true; } // try again, perhaps the id is already created if (checkProject(info.partClass())) { return true; } // Find first available custom part ID by taking the greatest // existing custom ID (if it exists) and adding 1. p_id = (int)KexiPart::UserObjectType; tristate success = d->connection->querySingleNumber("SELECT max(p_id) FROM kexi__parts", p_id); if (!success) { // Couldn't read part id's from the kexi__parts table return false; } else { // Got a maximum part ID, or there were no parts p_id = p_id + 1; p_id = qMax(p_id, (int)KexiPart::UserObjectType); } //this part's ID is not stored within kexi__parts: KexiDB::TableSchema *ts = d->connection->tableSchema("kexi__parts"); if (!ts) return false; QScopedPointer fl(ts->subList("p_id", "p_name", "p_mime", "p_url")); //kDebug() << "fieldlist: " << (fl ? fl->debugString() : QString()); if (!fl) return false; //kDebug() << info.ptr()->untranslatedGenericName(); // QStringList sl = part()->info()->ptr()->propertyNames(); // for (QStringList::ConstIterator it=sl.constBegin();it!=sl.constEnd();++it) // kDebug() << *it << " " << part()->info()->ptr()->property(*it).toString(); if (!d->connection->insertRecord( *fl, QVariant(p_id), QVariant(info.ptr()->untranslatedGenericName()), QVariant(QString::fromLatin1("kexi/") + info.objectName()/*ok?*/), QVariant(info.partClass() /*always ok?*/))) { return false; } //kDebug() << "insert success!"; d->saveClassId(info.partClass(), p_id); //kDebug() << "new id is: " << p_id; return true; } KexiPart::MissingPartsList KexiProject::missingParts() const { return d->missingParts; } static bool checkObjectId(const char* method, int objectID) { if (objectID <= 0) { kWarning() << method << ": Invalid objectID" << objectID; return false; } return true; } tristate KexiProject::loadUserDataBlock(int objectID, const QString& dataID, QString *dataString) { if (!checkObjectId("loadUserDataBlock", objectID)) { return false; } return d->connection->querySingleString( QString::fromLatin1("SELECT d_data FROM kexi__userdata WHERE o_id=") + QString::number(objectID) + " AND " + KexiDB::sqlWhere(d->connection->driver(), KexiDB::Field::Text, "d_user", d->userName()) + " AND " + KexiDB::sqlWhere(d->connection->driver(), KexiDB::Field::Text, "d_sub_id", dataID), *dataString); } bool KexiProject::storeUserDataBlock(int objectID, const QString& dataID, const QString &dataString) { if (!checkObjectId("storeUserDataBlock", objectID)) { return false; } QString sql(QString::fromLatin1( "SELECT kexi__userdata.o_id FROM kexi__userdata WHERE o_id=%1").arg(objectID)); QString sql_sub( KexiDB::sqlWhere(d->connection->driver(), KexiDB::Field::Text, "d_user", d->userName()) + " AND " + KexiDB::sqlWhere(d->connection->driver(), KexiDB::Field::Text, "d_sub_id", dataID)); bool ok; bool exists = d->connection->resultExists(sql + " AND " + sql_sub, ok); if (!ok) return false; if (exists) { return d->connection->executeSQL("UPDATE kexi__userdata SET d_data=" + d->connection->driver()->valueToSQL(KexiDB::Field::LongText, dataString) + " WHERE o_id=" + QString::number(objectID) + " AND " + sql_sub); } return d->connection->executeSQL( QString::fromLatin1("INSERT INTO kexi__userdata (d_user, o_id, d_sub_id, d_data) VALUES (") + d->connection->driver()->valueToSQL(KexiDB::Field::Text, d->userName()) + ", " + QString::number(objectID) + ", " + d->connection->driver()->valueToSQL(KexiDB::Field::Text, dataID) + ", " + d->connection->driver()->valueToSQL(KexiDB::Field::LongText, dataString) + ")"); } bool KexiProject::copyUserDataBlock(int sourceObjectID, int destObjectID, const QString &dataID) { if (!checkObjectId("storeUserDataBlock(sourceObjectID)", sourceObjectID)) { return false; } if (!checkObjectId("storeUserDataBlock(destObjectID)", destObjectID)) { return false; } if (sourceObjectID == destObjectID) return true; if (!removeUserDataBlock(destObjectID, dataID)) // remove before copying return false; QString sql(QString::fromLatin1( "INSERT INTO kexi__userdata SELECT t.d_user, %2, t.d_sub_id, t.d_data " "FROM kexi__userdata AS t WHERE d_user=%1 AND o_id=%3") .arg(d->connection->driver()->valueToSQL(KexiDB::Field::Text, d->userName())) .arg(destObjectID) .arg(sourceObjectID)); if (!dataID.isEmpty()) { sql += " AND " + KexiDB::sqlWhere(d->connection->driver(), KexiDB::Field::Text, "d_sub_id", dataID); } return d->connection->executeSQL(sql); } bool KexiProject::removeUserDataBlock(int objectID, const QString& dataID) { if (!checkObjectId("removeUserDataBlock", objectID)) { return false; } if (dataID.isEmpty()) return KexiDB::deleteRow(*d->connection, "kexi__userdata", "o_id", KexiDB::Field::Integer, objectID, "d_user", KexiDB::Field::Text, d->userName()); else return KexiDB::deleteRow(*d->connection, "kexi__userdata", "o_id", KexiDB::Field::Integer, objectID, "d_user", KexiDB::Field::Text, d->userName(), "d_sub_id", KexiDB::Field::Text, dataID); } // static bool KexiProject::askForOpeningNonWritableFileAsReadOnly(QWidget *parent, const QFileInfo &finfo) { KGuiItem openItem(KStandardGuiItem::open()); openItem.setText(i18n("Open As Read Only")); return KMessageBox::Yes == KMessageBox::questionYesNo( parent, i18nc("@info", "Could not open file %1 for reading and writing." "Do you want to open the file as read only?", QDir::convertSeparators(finfo.filePath())), i18nc("@title:window", "Could Not Open File" ), openItem, KStandardGuiItem::cancel(), QString()); } #include "kexiproject.moc" diff --git a/kexi/plugins/forms/kexiformview.cpp b/kexi/plugins/forms/kexiformview.cpp index f7c35ec0794..abb6a5ef422 100644 --- a/kexi/plugins/forms/kexiformview.cpp +++ b/kexi/plugins/forms/kexiformview.cpp @@ -1,1282 +1,1297 @@ /* This file is part of the KDE project Copyright (C) 2004 Cedric Pasteur Copyright (C) 2004-2011 Jarosław Staniek 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "widgets/kexidbform.h" #include "kexiformscrollview.h" #include "kexidatasourcepage.h" #include "kexiformmanager.h" #include "widgets/kexidbautofield.h" //! @todo #define KEXI_SHOW_SPLITTER_WIDGET class KexiFormView::Private { public: Private() : resizeMode(KexiFormView::ResizeDefault) , query(0) , queryIsOwned(false) , cursor(0) { } KexiDBForm *dbform; KexiFormScrollView *scrollView; /*! 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 (d->previousDataSourceString is used for that). */ QString previousDataSourceString; int resizeMode; KexiDB::QuerySchema* query; /*! True, if d->query is created as temporary object within this form. If user selected an existing, predefined (stored) query, d->queryIsOwned will be false, so the query object will not be destroyed. */ bool queryIsOwned; KexiDB::Cursor *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 delayedFormContentsResizeOnShow; //! Used in setFocusInternal() QPointer setFocusInternalOnce; #ifndef KEXI_NO_AUTOFIELD_WIDGET /*! 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 widgetGeometryForRecentInsertAutoFields; #endif //! Cached form pointer QPointer form; }; KexiFormView::KexiFormView(QWidget *parent, bool dbAware) : KexiDataAwareView(parent) , d(new Private) { Q_UNUSED(dbAware); d->delayedFormContentsResizeOnShow = 0; //! @todo remove? setSortedProperties(true); d->scrollView = new KexiFormScrollView( // will be added to layout this, viewMode() == Kexi::DataViewMode); // in KexiDataAwareView::init() initForm(); if (viewMode() == Kexi::DesignViewMode) { connect(form(), SIGNAL(propertySetSwitched()), this, SLOT(slotPropertySetSwitched())); connect(form(), SIGNAL(modified(bool)), this, SLOT(setDirty(bool))); connect(d->scrollView, SIGNAL(resized()), this, SLOT(setFormModified())); connect(d->dbform, SIGNAL(handleDragMoveEvent(QDragMoveEvent*)), this, SLOT(slotHandleDragMoveEvent(QDragMoveEvent*))); connect(d->dbform, SIGNAL(handleDropEvent(QDropEvent*)), this, SLOT(slotHandleDropEvent(QDropEvent*))); // action stuff plugSharedAction("formpart_taborder", form(), SLOT(editTabOrder())); plugSharedAction("formpart_adjust_size", form(), SLOT(adjustWidgetSize())); //! @todo add formpart_pixmap_collection action //! @todo add formpart_connections action plugSharedAction("edit_copy", form(), SLOT(copyWidget())); plugSharedAction("edit_cut", form(), SLOT(cutWidget())); plugSharedAction("edit_paste", form(), SLOT(pasteWidget())); plugSharedAction("edit_delete", form(), SLOT(deleteWidget())); plugSharedAction("edit_select_all", form(), SLOT(selectAll())); plugSharedAction("formpart_clear_contents", form(), SLOT(clearWidgetContent())); plugSharedAction("edit_undo", form(), SLOT(undo())); plugSharedAction("edit_redo", form(), SLOT(redo())); plugSharedAction("formpart_format_raise", form(), SLOT(bringWidgetToFront())); plugSharedAction("formpart_format_lower", form(), SLOT(sendWidgetToBack())); plugSharedAction("other_widgets_menu", form(), 0); setAvailable("other_widgets_menu", true); plugSharedAction("formpart_align_menu", form(), 0); plugSharedAction("formpart_align_to_left", form(), SLOT(alignWidgetsToLeft())); plugSharedAction("formpart_align_to_right", form(), SLOT(alignWidgetsToRight())); plugSharedAction("formpart_align_to_top", form(), SLOT(alignWidgetsToTop())); plugSharedAction("formpart_align_to_bottom", form(), SLOT(alignWidgetsToBottom())); plugSharedAction("formpart_align_to_grid", form(), SLOT(alignWidgetsToGrid())); plugSharedAction("formpart_adjust_size_menu", form(), 0); plugSharedAction("formpart_adjust_to_fit", form(), SLOT(adjustWidgetSize())); plugSharedAction("formpart_adjust_size_grid", form(), SLOT(adjustSizeToGrid())); plugSharedAction("formpart_adjust_height_small", form(), SLOT(adjustHeightToSmall())); plugSharedAction("formpart_adjust_height_big", form(), SLOT(adjustHeightToBig())); plugSharedAction("formpart_adjust_width_small", form(), SLOT(adjustWidthToSmall())); plugSharedAction("formpart_adjust_width_big", form(), SLOT(adjustWidthToBig())); plugSharedAction("format_font", form(), SLOT(changeFont())); // - setup local actions QList viewActions; QAction* a; a = form()->action("edit_undo"); a->setProperty("iconOnly", true); viewActions << a; a = form()->action("edit_redo"); a->setProperty("iconOnly", true); viewActions << a; setViewActions(viewActions); } KexiDataAwareView::init(d->scrollView, d->scrollView, d->scrollView, /* skip data-awarness if design mode */ viewMode() == Kexi::DesignViewMode); connect(this, SIGNAL(focus(bool)), this, SLOT(slotFocus(bool))); } KexiFormView::~KexiFormView() { if (d->cursor) { KexiDB::Connection *conn = KexiMainWindowIface::global()->project()->dbConnection(); conn->deleteCursor(d->cursor); d->cursor = 0; } deleteQuery(); propertySetSwitched(); delete d; } void KexiFormView::deleteQuery() { if (d->cursor) { KexiDB::Connection *conn = KexiMainWindowIface::global()->project()->dbConnection(); conn->deleteCursor(d->cursor); d->cursor = 0; } if (d->queryIsOwned) { delete d->query; } else { //! @todo remove this shared query from listened queries list } d->query = 0; } void KexiFormView::setForm(KFormDesigner::Form *f) { if (viewMode() == Kexi::DataViewMode) tempData()->previewForm = f; else tempData()->form = f; d->form = f; } void KexiFormView::initForm() { d->dbform = new KexiDBForm(d->scrollView->widget(), d->scrollView); if (viewMode() == Kexi::DataViewMode) { d->scrollView->setWidget(d->dbform); } else { d->scrollView->setMainAreaWidget(d->dbform); } d->dbform->setObjectName( i18nc("A prefix for identifiers of forms. Based on that, identifiers such as " "form1, form2 are generated. " "This string can be used to refer the widget object as variables in programming " "languages or macros so it must _not_ contain white spaces and non latin1 characters, " "should start with lower case letter and if there are subsequent words, these should " "start with upper case letter. Example: smallCamelCase. " "Moreover, try to make this prefix as short as possible.", "form")); QPalette pal(d->dbform->palette()); pal.setBrush(QPalette::Window, palette().brush(QPalette::Window)); d->dbform->setPalette(pal); // avoid inheriting QPalette::Window role d->scrollView->setResizingEnabled(true); if (viewMode() == Kexi::DataViewMode) { d->scrollView->recordNavigator()->setRecordHandler(d->scrollView); QPalette pal(d->scrollView->viewport()->palette()); pal.setBrush(d->scrollView->viewport()->backgroundRole(), d->dbform->palette().brush(d->dbform->backgroundRole())); d->scrollView->viewport()->setPalette(pal); } setForm( new KFormDesigner::Form( KexiFormManager::self()->library(), viewMode() == Kexi::DataViewMode ? KFormDesigner::Form::DataMode : KFormDesigner::Form::DesignMode, *KexiMainWindowIface::global()->actionCollection(), *KexiFormManager::self()->widgetActionGroup()) ); form()->createToplevel(d->dbform, d->dbform); const bool newForm = window()->id() < 0; KexiDB::FieldList *fields = 0; #ifndef NO_DSWIZARD if (newForm) { // Show the form wizard if this is a new Form KexiDataSourceWizard *w = new KexiDataSourceWizard( KexiMainWindowIface::global()->thisWidget()); if (!w->exec()) fields = 0; else fields = w->fields(); delete w; } #endif if (fields) { #ifndef NO_DSWIZARD QDomDocument dom; formPart()->generateForm(fields, dom); KFormDesigner::FormIO::loadFormFromDom(form(), d->dbform, dom); //! @todo handle errors #endif } else { loadForm(); } if (form()->autoTabStops()) form()->autoAssignTabStops(); //collect tab order information d->dbform->updateTabStopsOrder(form()); if (viewMode() == Kexi::DesignViewMode) { connect(form(), SIGNAL(widgetNameChanged(QByteArray,QByteArray)), this, SLOT(slotWidgetNameChanged(QByteArray,QByteArray))); connect(form(), SIGNAL(selectionChanged(QWidget*,KFormDesigner::Form::WidgetSelectionFlags)), this, SLOT(slotWidgetSelectionChanged(QWidget*,KFormDesigner::Form::WidgetSelectionFlags))); form()->selectWidget(form()->widget()); } else { form()->setMode(KFormDesigner::Form::DataMode); d->dbform->setMinimumSize(d->dbform->size()); // make vscrollbar appear when viewport is too small } d->scrollView->setForm(form()); d->scrollView->refreshContentsSize(); if (newForm && !fields) { /* Our form's area will be resized more than once. Let's resize form widget itself later. */ d->delayedFormContentsResizeOnShow = 3; } slotPropertySetSwitched(); // this prepares the data source page updateDataSourcePage(); if (!newForm && viewMode() == Kexi::DesignViewMode) { form()->clearUndoStack(); } } 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(d->dbform->dataSource()); QString dataSourcePartClassString(d->dbform->dataSourcePartClass()); KexiDB::Connection *conn = KexiMainWindowIface::global()->project()->dbConnection(); KexiDB::TableOrQuerySchema tableOrQuery( conn, dataSourceString.toLatin1(), dataSourcePartClassString == "org.kexi-project.table"); if (!tableOrQuery.table() && !tableOrQuery.query()) return; foreach (KFormDesigner::ObjectTreeItem *item, *form()->objectTree()->hash()) { KexiDBAutoField *afWidget = dynamic_cast(item->widget()); if (afWidget) { KexiDB::QueryColumnInfo *colInfo = tableOrQuery.columnInfo(afWidget->dataSource()); if (colInfo) { afWidget->setColumnInfo(colInfo); } } } } 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(d->dbform->dataSource()); QString dataSourcePartClassString(d->dbform->dataSourcePartClass()); KexiDB::Connection *conn = KexiMainWindowIface::global()->project()->dbConnection(); KexiDB::TableOrQuerySchema tableOrQuery( conn, dataSourceString.toLatin1(), dataSourcePartClassString == "org.kexi-project.table"); if (!tableOrQuery.table() && !tableOrQuery.query()) return; foreach (KFormDesigner::ObjectTreeItem *item, *form()->objectTree()->hash()) { // (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(item->widget()); if (subpropIface && subpropIface->subwidget() && item->subproperties()) { QWidget *subwidget = subpropIface->subwidget(); QHash* subprops = item->subproperties(); for (QHash::const_iterator subpropIt = subprops->constBegin(); subpropIt != subprops->constEnd(); ++subpropIt) { //kDebug() << "delayed setting of the subproperty: widget=" // << item->widget()->objectName() << " prop=" << subpropIt.key() << " val=" // << subpropIt.value(); QMetaProperty meta = KexiUtils::findPropertyWithSuperclasses( subwidget, subpropIt.key().toLatin1().constData()); if (meta.isValid()) { // 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.isEnumType() && subpropIt.value().type() == QVariant::StringList) { const QByteArray keysCombined(subpropIt.value().toStringList().join("|").toLatin1()); subwidget->setProperty(subpropIt.key().toLatin1(), meta.enumerator().keysToValue(keysCombined.constData())); } else { subwidget->setProperty(subpropIt.key().toLatin1(), subpropIt.value()); } } }//for } } } //! Used in KexiFormView::loadForm() static void setUnsavedBLOBIdsForDataViewMode( QWidget* widget, const QHash& unsavedLocalBLOBsByName) { if (widget) { if (-1 != widget->metaObject()->indexOfProperty("pixmapId")) { const KexiBLOBBuffer::Id_t blobID = unsavedLocalBLOBsByName.value(widget->objectName().toLatin1()); if (blobID > 0) //! @todo KexiBLOBBuffer::Id_t is unsafe and unsupported by QVariant - fix it widget->setProperty( "pixmapId", (uint)blobID); } const QList list(widget->findChildren()); if (list.isEmpty()) return; foreach(QWidget *w, list) { setUnsavedBLOBIdsForDataViewMode(w, unsavedLocalBLOBsByName); } } } void KexiFormView::loadForm() { //! @todo also load d->resizeMode //kDebug() << "Loading the form with id" << window()->id(); // 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(), d->dbform, tempData()->tempForm); setUnsavedBLOBIdsForDataViewMode(d->dbform, tempData()->unsavedLocalBLOBsByName); updateAutoFieldsDataSource(); updateValuesForSubproperties(); return; } // normal load QString data; loadDataBlock(data); KFormDesigner::FormIO::loadFormFromString(form(), d->dbform, data); //"autoTabStops" property is loaded -set it within the form tree as well form()->setAutoTabStops(d->dbform->autoTabStops()); updateAutoFieldsDataSource(); updateValuesForSubproperties(); } void KexiFormView::slotPropertySetSwitched() { propertySetReloaded(); if (viewMode() == Kexi::DesignViewMode) { formPart()->dataSourcePage()->assignPropertySet(&form()->propertySet()); } } tristate KexiFormView::beforeSwitchTo(Kexi::ViewMode mode, bool &dontStore) { if (mode != viewMode()) { if (viewMode() == Kexi::DataViewMode) { if (!d->scrollView->acceptRowEdit()) return cancelled; d->scrollView->beforeSwitchView(); } else { //remember our pos tempData()->scrollViewContentsPos = QPoint(d->scrollView->horizontalScrollBar()->value(), d->scrollView->verticalScrollBar()->value()); } } // we don't store on db, but in our TempData dontStore = true; if (isDirty() && (mode == Kexi::DataViewMode) && form()->objectTree()) { KexiFormPartTempData* 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 (QHash::const_iterator it = temp->unsavedLocalBLOBs.constBegin(); it != temp->unsavedLocalBLOBs.constEnd(); ++it) { if (!it.key()) continue; temp->unsavedLocalBLOBsByName.insert(it.key()->objectName().toLatin1(), it.value()); } } return true; } tristate KexiFormView::afterSwitchFrom(Kexi::ViewMode mode) { if (mode == 0 || mode == Kexi::DesignViewMode) { if (window()->neverSaved()) { d->scrollView->refreshContentsSizeLater(); } } if (mode != 0 && mode != Kexi::DesignViewMode) { //preserve contents pos after switching to other view d->scrollView->horizontalScrollBar()->setValue(tempData()->scrollViewContentsPos.x()); d->scrollView->verticalScrollBar()->setValue(tempData()->scrollViewContentsPos.y()); } if ((mode == Kexi::DesignViewMode) && viewMode() == Kexi::DataViewMode) { // The form may have been modified, so we must recreate the preview delete d->dbform; // also deletes form() initForm(); //reset position d->scrollView->horizontalScrollBar()->setValue(0); d->scrollView->verticalScrollBar()->setValue(0); d->dbform->move(0, 0); } //update tab stops if needed if (viewMode() == Kexi::DataViewMode) { } else { //set "autoTabStops" property d->dbform->setAutoTabStops(form()->autoTabStops()); } if (viewMode() == Kexi::DataViewMode) { //TMP!! - initDataSource(); + if (!initDataSource()) { + return false; + } //handle events for this form d->scrollView->setMainWidgetForEventHandling(d->dbform); //set focus on 1st focusable widget which has valid dataSource property set QList *orderedFocusWidgets = d->dbform->orderedFocusWidgets(); if (!orderedFocusWidgets->isEmpty()) { KexiUtils::unsetFocusWithReason(QApplication::focusWidget(), Qt::TabFocusReason); QWidget *widget = 0; foreach(widget, *orderedFocusWidgets) { KexiFormDataItemInterface *iface = dynamic_cast(widget); if (iface) { //kDebug() << iface->dataSource(); } if (iface && iface->columnInfo() && !iface->isReadOnly() /*! @todo add option for skipping autoincremented fields */ /* also skip autoincremented fields:*/ && !iface->columnInfo()->field->isAutoIncrement()) { break; } } if (!widget) //eventually, focus first available widget if nothing other is available widget = orderedFocusWidgets->first(); widget->setFocus(); KexiUtils::setFocusWithReason(widget, Qt::TabFocusReason); d->setFocusInternalOnce = widget; } if (d->query) d->scrollView->selectFirstRow(); } //dirty only if it's a new object if (mode == Kexi::NoViewMode) setDirty(window()->partItem()->neverSaved()); updateActionsInternal(); return true; } KoProperty::Set* KexiFormView::propertySet() { return &d->form->propertySet(); } KexiFormPartTempData* KexiFormView::tempData() const { return dynamic_cast(window()->data()); } KexiFormPart* KexiFormView::formPart() const { return dynamic_cast(part()); } -void KexiFormView::initDataSource() +bool KexiFormView::initDataSource() { deleteQuery(); + const QString dataSourceString(d->dbform->dataSource()); + if (dataSourceString.isEmpty()) { + return true; // nothing to do + } + //! @todo also handle anonymous (not stored) queries provided as statements here KexiDB::TableSchema *tableSchema = 0; KexiDB::Connection *conn = 0; QStringList sources; bool forceReadOnlyDataSource = false; - QString dataSourceString(d->dbform->dataSource()); - bool ok = !dataSourceString.isEmpty(); + bool ok = true; if (ok) { //collect all data-aware widgets and create query schema d->scrollView->setMainDataSourceWidget(d->dbform); sources = d->scrollView->usedDataSources(); conn = KexiMainWindowIface::global()->project()->dbConnection(); QString dataSourcePartClassString(d->dbform->dataSourcePartClass()); if (dataSourcePartClassString.isEmpty() /*table type is the default*/ || dataSourcePartClassString == "org.kexi-project.table") { tableSchema = conn->tableSchema(dataSourceString); if (tableSchema) { /* We will build a _minimud-> query schema from selected table fields. */ d->query = new KexiDB::QuerySchema(); d->queryIsOwned = true; if (dataSourcePartClassString.isEmpty()) d->dbform->setDataSourcePartClass("org.kexi-project.table"); //update for compatibility } } if (!tableSchema) { if (dataSourcePartClassString.isEmpty() /*also try to find a query (for compatibility with Kexi<=0.9)*/ || dataSourcePartClassString == "org.kexi-project.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.) d->query = conn->querySchema(dataSourceString); d->queryIsOwned = false; ok = d->query != 0; if (ok && dataSourcePartClassString.isEmpty()) d->dbform->setDataSourcePartClass("org.kexi-project.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 classes are supported ok = false; } } } QSet invalidSources; 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(); //kDebug() << "pkey added to data sources:" << pkey->names(); } //kDebug() << "sources=" << sources; uint index = 0; for (QStringList::ConstIterator it = sources.constBegin(); it != sources.constEnd(); ++it, index++) { /*! @todo add expression support */ QString fieldName((*it).toLower()); //remove "tablename." if it was prepended if (tableSchema && fieldName.startsWith(tableSchema->name() + QLatin1Char('.'), Qt::CaseInsensitive)) fieldName.remove(0, tableSchema->name().length() + 1); //remove "queryname." if it was prepended if (!tableSchema && fieldName.startsWith(d->query->name() + QLatin1Char('.'), Qt::CaseInsensitive)) fieldName.remove(0, d->query->name().length() + 1); KexiDB::Field *f = tableSchema ? tableSchema->field(fieldName) : d->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); //kDebug() << "invalidSources+=" << index << " (" << (*it) << ")"; continue; } if (tableSchema) { if (!d->query->hasField(f)) { //we're building a new query: add this field d->query->addField(f); } } } if (invalidSources.count() == sources.count()) { //all data sources are invalid! don't execute the query deleteQuery(); } else { KexiDB::debug(d->query->parameters()); // like in KexiQueryView::executeQuery() QList params; { KexiUtils::WaitCursorRemover remover; params = KexiQueryParameters::getParameters(this, *conn->driver(), *d->query, ok); } - if (ok) //input cancelled - d->cursor = conn->executeQuery(*d->query, params); + if (ok) { //input cancelled + d->cursor = conn->prepareQuery(*d->query, params); + } } d->scrollView->invalidateDataSources(invalidSources, d->query); ok = d->cursor != 0; + if (ok) { + ok = setData(d->cursor); + } } if (!invalidSources.isEmpty()) d->dbform->updateTabStopsOrder(); if (ok) { //! @todo PRIMITIVE!! data setting: //! @todo KexiDB::TableViewData is not a great name for data class here... rename/move? KexiDB::TableViewData* data = new KexiDB::TableViewData(d->cursor); if (forceReadOnlyDataSource) data->setReadOnly(true); - data->preloadAllRows(); + ok = data->preloadAllRows(); ///*! @todo few backends return result count for free! - no need to reopen() */ // int resultCount = -1; // if (ok) { // resultCount = d->conn->resultCount(d->conn->selectStatement(*d->query)); // ok = d->cursor->reopen(); // } // if (ok) // ok = ! (!d->cursor->moveFirst() && d->cursor->error()); - d->scrollView->setData(data, true /*owner*/); + if (ok) { + d->scrollView->setData(data, true /*owner*/); + } else { + delete data; + } } else { d->scrollView->setData(0, false); } + return ok; } void KexiFormView::setFormModified() { form()->setModified(true); } KexiDB::SchemaData* KexiFormView::storeNewData(const KexiDB::SchemaData& sdata, KexiView::StoreNewDataOptions options, bool &cancel) { KexiDB::SchemaData *s = KexiView::storeNewData(sdata, options, cancel); //kDebug() << "new id:" << s->id(); if (!s || cancel) { delete s; return 0; } if (!storeData()) { //failure: remove object's schema data to avoid garbage KexiDB::Connection *conn = KexiMainWindowIface::global()->project()->dbConnection(); conn->removeObject(s->id()); delete s; return 0; } return s; } tristate KexiFormView::storeData(bool dontAsk) { Q_UNUSED(dontAsk); //kDebug() << window()->partItem()->name() << "[" << window()->id() << "]"; //-- first, store local BLOBs, so identifiers can be updated //! @todo remove unused data stored previously KexiDB::Connection *conn = KexiMainWindowIface::global()->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( window()->viewForMode(Kexi::DesignViewMode)); if (designFormView) { for (QHash::const_iterator it = tempData()->unsavedLocalBLOBs.constBegin(); it != tempData()->unsavedLocalBLOBs.constEnd(); ++it) { if (!it.key()) { kWarning() << "it.key()==0 !"; continue; } //kDebug() << "name=" << it.key()->objectName() << " dataID=" << it.value(); KexiBLOBBuffer::Handle h(blobBuf->objectForId(it.value(), /*!stored*/false)); if (!h) continue; //no BLOB assigned QString originalFileName(h.originalFileName()); QFileInfo fi(originalFileName); QString caption(fi.baseName().replace('_', ' ').simplified()); 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; kWarning() << "execute error"; return false; } } delete blobsFieldsWithoutID; blobsFieldsWithoutID = 0; const quint64 storedBLOBID = conn->lastInsertedAutoIncValue("o_id", "kexi__blobs"); if ((quint64) - 1 == storedBLOBID) { //! @todo show message? return false; } //kDebug() << "storedDataID=" << storedBLOBID; //! @todo unsafe - fix! h.setStoredWidthID((KexiBLOBBuffer::Id_t)storedBLOBID); //set widget's internal property so it can be saved... const QVariant oldStoredPixmapId(it.key()->property("storedPixmapId")); //! @todo KexiBLOBBuffer::Id_t is unsafe and unsupported by QVariant - fix! it.key()->setProperty("storedPixmapId", QVariant((uint)storedBLOBID)); KFormDesigner::ObjectTreeItem *widgetItem = designFormView->form()->objectTree()->lookup(it.key()->objectName()); if (widgetItem) widgetItem->addModifiedProperty("storedPixmapId", oldStoredPixmapId); else kWarning() << "no" << widgetItem->name() << "widget found within a form"; } } //-- 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.clear(); return true; } //! @todo reuse the action stuff code #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; } } void KexiFormView::slotFormWidgetSelected(KFormDesigner::Form *f) { if (f != form()) return; disableWidgetActions(); enableFormActions(); } 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); #ifdef __GNUC__ #warning "Port this.." #else #pragma WARNING( Port this.. ) #endif //! @todo 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); } void KexiFormView::setUndoEnabled(bool enabled) { setAvailable("edit_undo", enabled); } void KexiFormView::setRedoEnabled(bool enabled) { setAvailable("edit_redo", enabled); } #endif //0 int KexiFormView::resizeMode() const { return d->resizeMode; } KFormDesigner::Form* KexiFormView::form() const { return d->form; } QSize KexiFormView::preferredSizeHint(const QSize& otherSize) { return (d->dbform->size() + QSize(d->scrollView->verticalScrollBar()->isVisible() ? d->scrollView->verticalScrollBar()->width()*3 / 2 : 10, d->scrollView->horizontalScrollBar()->isVisible() ? d->scrollView->horizontalScrollBar()->height()*3 / 2 : 10)) .expandedTo(KexiView::preferredSizeHint(otherSize)); } void KexiFormView::resizeEvent(QResizeEvent *e) { if (viewMode() == Kexi::DataViewMode) { d->scrollView->refreshContentsSizeLater(); } KexiView::resizeEvent(e); if (d->delayedFormContentsResizeOnShow > 0) { d->delayedFormContentsResizeOnShow--; d->dbform->resize(e->size() - QSize(30, 30)); } } void KexiFormView::contextMenuEvent(QContextMenuEvent *e) { // kDebug() << form()->selectedWidget() << form()->widget() << e->reason(); if (form()->selectedWidget() && form()->selectedWidget() == form()->widget() && e->reason() == QContextMenuEvent::Keyboard) { // Outer form area received context key. // Redirect the event to top-level form widget. // It will be received in Container::eventFilter(). e->accept(); QContextMenuEvent me(QContextMenuEvent::Keyboard, QPoint(-1, -1)); QApplication::sendEvent(form()->widget(), &me); return; } KexiView::contextMenuEvent(e); } void KexiFormView::setFocusInternal() { if (viewMode() == Kexi::DataViewMode) { if (d->dbform->focusWidget()) { //better-looking focus if (d->setFocusInternalOnce) { KexiUtils::setFocusWithReason(d->setFocusInternalOnce, Qt::OtherFocusReason); d->setFocusInternalOnce = 0; } else { //ok? SET_FOCUS_USING_REASON(d->dbform->focusWidget(), QFocusEvent::Other); } return; } } QWidget::setFocus(); } void KexiFormView::slotFocus(bool in) { Q_UNUSED(in); } void KexiFormView::updateDataSourcePage() { if (viewMode() == Kexi::DesignViewMode) { KoProperty::Set &set = form()->propertySet(); const QString dataSourcePartClass = set.propertyValue("dataSourcePartClass").toString(); const QString dataSource = set.propertyValue("dataSource").toString(); formPart()->dataSourcePage()->setFormDataSource(dataSourcePartClass, dataSource); } } void KexiFormView::slotHandleDragMoveEvent(QDragMoveEvent* e) { if (KexiFieldDrag::canDecode(e)) { e->setAccepted(true); } } void KexiFormView::slotHandleDropEvent(QDropEvent* e) { #ifdef KEXI_NO_AUTOFIELD_WIDGET Q_UNUSED(e); #else const QWidget *targetContainerWidget = dynamic_cast(sender()); KFormDesigner::ObjectTreeItem *targetContainerWidgetItem = targetContainerWidget ? form()->objectTree()->lookup(targetContainerWidget->objectName()) : 0; if (targetContainerWidgetItem && targetContainerWidgetItem->container() && KexiFieldDrag::canDecode(e)) { QString sourcePartClass, sourceName; QStringList fields; if (!KexiFieldDrag::decode(e, &sourcePartClass, &sourceName, &fields)) return; insertAutoFields(sourcePartClass, sourceName, fields, targetContainerWidgetItem->container(), e->pos()); } #endif } void KexiFormView::insertAutoFields(const QString& sourcePartClass, const QString& sourceName, const QStringList& fields, KFormDesigner::Container* targetContainer, const QPoint& _pos) { #ifdef KEXI_NO_AUTOFIELD_WIDGET Q_UNUSED(sourcePartClass); Q_UNUSED(sourceName); Q_UNUSED(fields); Q_UNUSED(targetContainer); Q_UNUSED(_pos); #else if (fields.isEmpty()) return; KexiDB::Connection *conn = KexiMainWindowIface::global()->project()->dbConnection(); KexiDB::TableOrQuerySchema tableOrQuery(conn, sourceName.toLatin1(), sourcePartClass == "org.kexi-project.table"); if (!tableOrQuery.table() && !tableOrQuery.query()) { kWarning() << "no such table/query" << sourceName; return; } QPoint pos(_pos); //if pos is not specified, compute a new position: if (pos == QPoint(-1, -1)) { if (d->widgetGeometryForRecentInsertAutoFields.isValid()) { pos = d->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 //! todo unnamed query columns are not supported QWidgetList widgetsToSelect; KFormDesigner::PropertyCommandGroup *group = new KFormDesigner::PropertyCommandGroup( fields.count() == 1 ? futureI18n("Insert AutoField widget") : futureI18n2("Insert %1 AutoField widgets", fields.count()) ); foreach(const QString& field, fields) { KexiDB::QueryColumnInfo* column = tableOrQuery.columnInfo(field); if (!column) { kWarning() << "no such field" << field << "in table/query" << sourceName; continue; } //! todo add autolabel using field's caption or name 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(), group ); insertCmd->redo(); KFormDesigner::ObjectTreeItem *newWidgetItem = form()->objectTree()->hash()->value(insertCmd->widgetName()); KexiDBAutoField* newWidget = newWidgetItem ? dynamic_cast(newWidgetItem->widget()) : 0; widgetsToSelect.append(newWidget); KFormDesigner::PropertyCommandGroup *subGroup = new KFormDesigner::PropertyCommandGroup( QString(), group); QHash propValues; propValues.insert("dataSource", column->aliasOrName()); propValues.insert("fieldTypeInternal", (int)column->field->type()); propValues.insert("fieldCaptionInternal", column->captionOrAliasOrName()); form()->createPropertyCommandsInDesignMode( newWidget, propValues, subGroup, false/*!addToActiveForm*/); subGroup->redo(); //set data source and caption //-we don't need to use PropertyCommand here beacause we don't need UNDO // for these single commands //resize again because autofield's type changed what can lead to changed sizeHint() QWidgetList list; list.append(newWidget); KFormDesigner::AdjustSizeCommand *adjustCommand = new KFormDesigner::AdjustSizeCommand( *form(), KFormDesigner::AdjustSizeCommand::SizeToFit, list, group); adjustCommand->redo(); 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(d->dbform->geometry()); QRect newFormRect(oldFormRect); newFormRect.setWidth(qMax(d->dbform->width(), widgetsToSelect.last()->geometry().right() + 1)); newFormRect.setHeight(qMax(d->dbform->height(), widgetsToSelect.last()->geometry().bottom() + 1)); if (newFormRect != oldFormRect) { //1. resize by hand d->dbform->setGeometry(newFormRect); //2. store information about resize (void)new KFormDesigner::PropertyCommand( *form(), d->dbform->objectName().toLatin1(), oldFormRect, newFormRect, "geometry", group); } //remember geometry of the last inserted widget d->widgetGeometryForRecentInsertAutoFields = widgetsToSelect.last()->geometry(); } //eventually, add entire command group to active form form()->addCommand(group); //kDebug() << *group; d->scrollView->widget()->update(); d->scrollView->refreshContentsSize(); //select all inserted widgets, if multiple if (widgetsToSelect.count() > 1) { form()->selectWidget(0); foreach (QWidget *w, widgetsToSelect) { form()->selectWidget(w, KFormDesigner::Form::AddToPreviousSelection | KFormDesigner::Form::DontRaise); } } //! @todo eventually, update property pane #endif } 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); } void KexiFormView::updateActions(bool activated) { if (viewMode()==Kexi::DesignViewMode) { if (activated) { form()->emitActionSignals(); formPart()->widgetTreePage()->setForm(form()); } } KexiDataAwareView::updateActions(activated); updateActionsInternal(); } void KexiFormView::slotWidgetNameChanged(const QByteArray& oldname, const QByteArray& newname) { Q_UNUSED(oldname); Q_UNUSED(newname); //kDebug() << oldname << newname << form()->propertySet().propertyValue("objectName").toString(); KexiMainWindowIface::global()->updatePropertyEditorInfoLabel(); formPart()->dataSourcePage()->updateInfoLabelForPropertySet(&form()->propertySet()); } void KexiFormView::slotWidgetSelectionChanged(QWidget *w, KFormDesigner::Form::WidgetSelectionFlags flags) { Q_UNUSED(w) Q_UNUSED(flags) updateActionsInternal(); } void KexiFormView::updateActionsInternal() { const QWidget* selectedWidget = form()->selectedWidget(); //kDebug() << selectedWidget << (viewMode()==Kexi::DesignViewMode) << widget_assign_action; QByteArray wClass; if (selectedWidget) { wClass = selectedWidget->metaObject()->className(); //kDebug() << wClass; } QAction *widget_assign_action = KexiFormManager::self()->action("widget_assign_action"); if (widget_assign_action) { widget_assign_action->setEnabled( viewMode()==Kexi::DesignViewMode && selectedWidget && (wClass == "QPushButton" || wClass == "KPushButton" || wClass == "KexiDBPushButton" || wClass == "KexiPushButton" || wClass == "KexiDBCommandLinkButton") ); } #ifdef KEXI_DEBUG_GUI QAction *show_form_ui_action = KexiFormManager::self()->action("show_form_ui"); if (show_form_ui_action) { show_form_ui_action->setEnabled(viewMode()==Kexi::DesignViewMode); } #endif } #include "kexiformview.moc" diff --git a/kexi/plugins/forms/kexiformview.h b/kexi/plugins/forms/kexiformview.h index 1dd7d306214..ecc46d0e63a 100644 --- a/kexi/plugins/forms/kexiformview.h +++ b/kexi/plugins/forms/kexiformview.h @@ -1,179 +1,179 @@ /* This file is part of the KDE project Copyright (C) 2004 Cedric Pasteur Copyright (C) 2004-2011 Jarosław Staniek 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 #include #include #include #include #include #include #include #include #include "kexiformpart.h" #define NO_DSWIZARD class KexiFormPart; class KexiFormPartTempData; namespace KexiDB { } 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 KexiWindow. 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 KexiDataTableView */ class KEXIFORMUTILS_EXPORT KexiFormView : public KexiDataAwareView { Q_OBJECT public: enum ResizeMode { ResizeAuto = 0, ResizeDefault = ResizeAuto, ResizeFixed = 1, NoResize = 2 /*! @todo */ }; explicit KexiFormView(QWidget *parent, bool dbAware = true); virtual ~KexiFormView(); virtual QSize preferredSizeHint(const QSize& otherSize); int resizeMode() const; 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 Q_SLOTS: /*! Inserts autofields onto the form at \a pos position. \a sourcePartClass can be "org.kexi-project.table" or "org.kexi-project.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& sourcePartClass, const QString& sourceName, const QStringList& fields, KFormDesigner::Container* targetContainerWidget, const QPoint& pos = QPoint(-1, -1)); protected Q_SLOTS: void slotPropertySetSwitched(); void setFormModified(); 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); void slotWidgetSelectionChanged(QWidget *w, KFormDesigner::Form::WidgetSelectionFlags flags); void slotWidgetNameChanged(const QByteArray& oldname, const QByteArray& newname); protected: virtual tristate beforeSwitchTo(Kexi::ViewMode mode, bool &dontStore); virtual tristate afterSwitchFrom(Kexi::ViewMode mode); virtual KoProperty::Set* propertySet(); virtual KexiDB::SchemaData* storeNewData(const KexiDB::SchemaData& sdata, KexiView::StoreNewDataOptions options, bool &cancel); virtual tristate storeData(bool dontAsk = false); KexiFormPartTempData* tempData() const; KexiFormPart* formPart() const; void setForm(KFormDesigner::Form *f); void initForm(); void loadForm(); //! Used in loadForm() void updateAutoFieldsDataSource(); //! Used in loadForm() void updateValuesForSubproperties(); virtual void resizeEvent(QResizeEvent *); //! Reimplemented for context key event of top-level form widget. //! Redirects to Container::eventFilter(). virtual void contextMenuEvent(QContextMenuEvent *e); - void initDataSource(); + bool initDataSource(); virtual void setFocusInternal(); /*! Called after loading the form contents (before showing it). */ void updateTabStopsOrder(); /*! @internal */ void deleteQuery(); /*! @internal */ void updateDataSourcePage(); /*! Reimplemented after KexiView. Updates actions (e.g. availability). */ virtual void updateActions(bool activated); //! Updates internal actions specific to forms. //! @todo merge with other "update" routines? void updateActionsInternal(); private: class Private; Private * const d; }; #endif diff --git a/kexi/plugins/queries/kexiqueryview.cpp b/kexi/plugins/queries/kexiqueryview.cpp index dd3915d88f0..1e233ad5683 100644 --- a/kexi/plugins/queries/kexiqueryview.cpp +++ b/kexi/plugins/queries/kexiqueryview.cpp @@ -1,159 +1,166 @@ /* This file is part of the KDE project Copyright (C) 2004 Lucijan Busch Copyright (C) 2004, 2006 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include "kexiqueryview.h" #include "kexiquerydesignersql.h" #include "kexiquerydesignerguieditor.h" #include "kexiquerypart.h" #include #include //! @internal class KexiQueryView::Private { public: Private() : cursor(0), currentParams() {} ~Private() {} KexiDB::Cursor *cursor; QList currentParams; /*! Used in storeNewData(), storeData() to decide whether we should ask other view to save changes. Stores information about view mode. */ }; //--------------------------------------------------------------------------------- KexiQueryView::KexiQueryView(QWidget *parent) : KexiDataTableView(parent) , d(new Private()) { // setup main menu actions QList mainMenuActions; mainMenuActions << sharedAction("project_export_data_table"); setMainMenuActions(mainMenuActions); 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; KexiDB::Connection * conn = KexiMainWindowIface::global()->project()->dbConnection(); { KexiUtils::WaitCursorRemover remover; d->currentParams = KexiQueryParameters::getParameters(this, *conn->driver(), *query, ok); } if (!ok) {//input cancelled return cancelled; } - d->cursor = conn->executeQuery(*query, d->currentParams); + d->cursor = conn->prepareQuery(*query, d->currentParams); if (!d->cursor) { window()->setStatus( conn, i18n("Query executing failed.")); //! @todo also provide server result and sql statement return false; } - setData(d->cursor); + ok = setData(d->cursor); //! @todo remove close() when dynamic cursors arrive - d->cursor->close(); - - if (oldCursor) - oldCursor->connection()->deleteCursor(oldCursor); + if (!d->cursor->close()) { + ok = false; + } + if (oldCursor) { + conn->deleteCursor(oldCursor); + } + if (!ok) { + conn->deleteCursor(d->cursor); + d->cursor = 0; + return false; + } //! @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(Kexi::ViewMode mode) { if (mode == Kexi::NoViewMode) { KexiDB::QuerySchema *querySchema = static_cast(window()->schemaData()); const tristate result = executeQuery(querySchema); if (true != result) return result; } else if (mode == Kexi::DesignViewMode || Kexi::TextViewMode) { KexiQueryPart::TempData * temp = static_cast(window()->data()); const tristate result = executeQuery(temp->query()); if (true != result) return result; } return true; } KexiDB::SchemaData* KexiQueryView::storeNewData(const KexiDB::SchemaData& sdata, KexiView::StoreNewDataOptions options, bool &cancel) { KexiView * view = window()->viewThatRecentlySetDirtyFlag(); if (dynamic_cast(view)) return dynamic_cast(view)->storeNewData(sdata, options, cancel); if (dynamic_cast(view)) return dynamic_cast(view)->storeNewData(sdata, options, cancel); return 0; } tristate KexiQueryView::storeData(bool dontAsk) { KexiView * view = window()->viewThatRecentlySetDirtyFlag(); if (dynamic_cast(view)) return dynamic_cast(view)->storeData(dontAsk); if (dynamic_cast(view)) return dynamic_cast(view)->storeData(dontAsk); return false; } QList KexiQueryView::currentParameters() const { return d->currentParams; } #include "kexiqueryview.moc" diff --git a/kexi/plugins/reports/kexidbreportdata.cpp b/kexi/plugins/reports/kexidbreportdata.cpp index 25b1db8830a..72c405d102b 100644 --- a/kexi/plugins/reports/kexidbreportdata.cpp +++ b/kexi/plugins/reports/kexidbreportdata.cpp @@ -1,400 +1,404 @@ /* * Kexi Report Plugin * Copyright (C) 2007-2009 by Adam Pigg (adam@piggz.co.uk) +* Copyright (C) 2015 Jarosław Staniek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "kexidbreportdata.h" -#include +#include "kexireportview.h" #include #include -#include +#include + +#include class KexiDBReportData::Private { public: - explicit Private(KexiDB::Connection *pDb) - : cursor(0), connection(pDb), originalSchema(0), copySchema(0) + explicit Private(KexiDB::Connection *pDb, KexiReportView *v) + : cursor(0), connection(pDb), view(v), originalSchema(0), copySchema(0) { } ~Private() { delete copySchema; delete originalSchema; delete cursor; } - QString objectName; - KexiDB::Cursor *cursor; - KexiDB::Connection *connection; + KexiDB::Connection * const connection; + KexiReportView * const view; KexiDB::QuerySchema *originalSchema; KexiDB::QuerySchema *copySchema; }; KexiDBReportData::KexiDBReportData (const QString &objectName, - KexiDB::Connection * pDb) - : d(new Private(pDb)) + KexiDB::Connection * pDb, KexiReportView *view) + : d(new Private(pDb, view)) { d->objectName = objectName; getSchema(); } KexiDBReportData::KexiDBReportData(const QString& objectName, const QString& partClass, - KexiDB::Connection* pDb) - : d(new Private(pDb)) + KexiDB::Connection* pDb, KexiReportView *view) + : d(new Private(pDb, view)) { d->objectName = objectName; getSchema(partClass); } void KexiDBReportData::setSorting(const QList& sorting) { if (d->copySchema) { if (sorting.isEmpty()) return; KexiDB::OrderByColumnList order; for (int i = 0; i < sorting.count(); i++) { order.appendField(*d->copySchema, sorting[i].field, sorting[i].order == Qt::AscendingOrder); } d->copySchema->setOrderByColumnList(order); } else { kDebug() << "Unable to sort null schema"; } } void KexiDBReportData::addExpression(const QString& field, const QVariant& value, int relation) { if (d->copySchema) { KexiDB::Field *fld = d->copySchema->findTableField(field); if (fld) { d->copySchema->addToWhereExpression(fld, value, relation); } } else { kDebug() << "Unable to add expresstion to null schema"; } } KexiDBReportData::~KexiDBReportData() { close(); delete d; } bool KexiDBReportData::open() { if ( d->connection && d->cursor == 0 ) { if ( d->objectName.isEmpty() ) { - d->cursor = d->connection->executeQuery ( "SELECT '' AS expr1 FROM kexi__db WHERE kexi__db.db_property = 'kexidb_major_ver'" ); + d->cursor = d->connection->prepareQuery("SELECT '' AS expr1 FROM kexi__db WHERE kexi__db.db_property = 'kexidb_major_ver'"); } else if ( d->copySchema) { kDebug() << "Opening cursor.." << d->copySchema->debugString(); - d->cursor = d->connection->executeQuery ( *d->copySchema, 1 ); + d->cursor = d->connection->prepareQuery(*d->copySchema, KexiDB::Cursor::Buffered); } - - if ( d->cursor ) - { - kDebug() << "Moving to first record.."; - return d->cursor->moveFirst(); + if (d->cursor) { + bool ok = d->view->setData(d->cursor); + if (ok) { + kDebug() << "Moving to first record.."; + if (!d->cursor->moveFirst()) { + ok = !d->cursor->error(); + } + } + return ok; } - else - return false; } return false; } bool KexiDBReportData::close() { if ( d->cursor ) { d->cursor->close(); delete d->cursor; d->cursor = 0; } return true; } bool KexiDBReportData::getSchema(const QString& partClass) { if (d->connection) { delete d->originalSchema; d->originalSchema = 0; delete d->copySchema; d->copySchema = 0; if ((partClass.isEmpty() || partClass == "org.kexi-project.table") && d->connection->tableSchema(d->objectName)) { kDebug() << d->objectName << "is a table.."; d->originalSchema = new KexiDB::QuerySchema(*(d->connection->tableSchema(d->objectName))); } else if ((partClass.isEmpty() || partClass == "org.kexi-project.query") && d->connection->querySchema(d->objectName)) { kDebug() << d->objectName << "is a query.."; d->connection->querySchema(d->objectName)->debug(); d->originalSchema = new KexiDB::QuerySchema(*(d->connection->querySchema(d->objectName))); } if (d->originalSchema) { kDebug() << "Original:" << d->connection->selectStatement(*d->originalSchema); d->originalSchema->debug(); d->copySchema = new KexiDB::QuerySchema(*d->originalSchema); d->copySchema->debug(); kDebug() << "Copy:" << d->connection->selectStatement(*d->copySchema); } return true; } return false; } QString KexiDBReportData::sourceName() const { return d->objectName; } int KexiDBReportData::fieldNumber ( const QString &fld ) const { if (!d->cursor || !d->cursor->query()) { return -1; } const KexiDB::QueryColumnInfo::Vector fieldsExpanded( d->cursor->query()->fieldsExpanded(KexiDB::QuerySchema::Unique)); for (int i = 0; i < fieldsExpanded.size() ; ++i) { if (0 == QString::compare(fld, fieldsExpanded[i]->aliasOrName(), Qt::CaseInsensitive)) { return i; } } return -1; } QStringList KexiDBReportData::fieldNames() const { if (!d->originalSchema) { return QStringList(); } QStringList names; const KexiDB::QueryColumnInfo::Vector fieldsExpanded( d->originalSchema->fieldsExpanded(KexiDB::QuerySchema::Unique)); for (int i = 0; i < fieldsExpanded.size(); i++) { //! @todo in some Kexi mode captionOrAliasOrName() would be used here (more user-friendly) names.append(fieldsExpanded[i]->aliasOrName()); } return names; } QVariant KexiDBReportData::value ( unsigned int i ) const { if ( d->cursor ) return d->cursor->value ( i ); return QVariant(); } QVariant KexiDBReportData::value ( const QString &fld ) const { int i = fieldNumber ( fld ); if ( d->cursor ) return d->cursor->value ( i ); return QVariant(); } bool KexiDBReportData::moveNext() { if ( d->cursor ) return d->cursor->moveNext(); return false; } bool KexiDBReportData::movePrevious() { if ( d->cursor ) return d->cursor->movePrev(); return false; } bool KexiDBReportData::moveFirst() { if ( d->cursor ) return d->cursor->moveFirst(); return false; } bool KexiDBReportData::moveLast() { if ( d->cursor ) return d->cursor->moveLast(); return false; } qint64 KexiDBReportData::at() const { if ( d->cursor ) return d->cursor->at(); return 0; } qint64 KexiDBReportData::recordCount() const { if ( d->copySchema ) { return KexiDB::rowCount ( *d->copySchema ); } else { return 1; } } QStringList KexiDBReportData::scriptList(const QString& interpreter) const { QStringList scripts; if( d->connection) { QList scriptids = d->connection->objectIds(KexiPart::ScriptObjectType); QStringList scriptnames = d->connection->objectNames(KexiPart::ScriptObjectType); QString script; int i; i = 0; kDebug() << scriptids << scriptnames; kDebug() << interpreter; //A blank entry scripts << ""; foreach(int id, scriptids) { kDebug() << "ID:" << id; tristate res; res = d->connection->loadDataBlock(id, script, QString()); if (res == true) { QDomDocument domdoc; bool parsed = domdoc.setContent(script, false); QDomElement scriptelem = domdoc.namedItem("script").toElement(); if (parsed && !scriptelem.isNull()) { if (interpreter == scriptelem.attribute("language") && scriptelem.attribute("scripttype") == "object") { scripts << scriptnames[i]; } } else { kDebug() << "Unable to parse script"; } } else { kDebug() << "Unable to loadDataBlock"; } ++i; } kDebug() << scripts; } return scripts; } QString KexiDBReportData::scriptCode(const QString& scriptname, const QString& language) const { QString scripts; if (d->connection) { QList scriptids = d->connection->objectIds(KexiPart::ScriptObjectType); QStringList scriptnames = d->connection->objectNames(KexiPart::ScriptObjectType); int i = 0; QString script; foreach(int id, scriptids) { kDebug() << "ID:" << id; tristate res; res = d->connection->loadDataBlock(id, script, QString()); if (res == true) { QDomDocument domdoc; bool parsed = domdoc.setContent(script, false); if (! parsed) { kDebug() << "XML parsing error"; return QString(); } QDomElement scriptelem = domdoc.namedItem("script").toElement(); if (scriptelem.isNull()) { kDebug() << "script domelement is null"; return QString(); } QString interpretername = scriptelem.attribute("language"); kDebug() << language << interpretername; kDebug() << scriptelem.attribute("scripttype"); kDebug() << scriptname << scriptnames[i]; if (language == interpretername && (scriptelem.attribute("scripttype") == "module" || scriptname == scriptnames[i])) { scripts += '\n' + scriptelem.text().toUtf8(); } ++i; } else { kDebug() << "Unable to loadDataBlock"; } } } return scripts; } QStringList KexiDBReportData::dataSources() const { //Get the list of queries in the database QStringList qs; if (d->connection && d->connection->isConnected()) { QList tids = d->connection->tableIds(); qs << ""; for (int i = 0; i < tids.size(); ++i) { KexiDB::TableSchema* tsc = d->connection->tableSchema(tids[i]); if (tsc) qs << tsc->name(); } QList qids = d->connection->queryIds(); qs << ""; for (int i = 0; i < qids.size(); ++i) { KexiDB::QuerySchema* qsc = d->connection->querySchema(qids[i]); if (qsc) qs << qsc->name(); } } return qs; } KoReportData* KexiDBReportData::create(const QString& source) { - return new KexiDBReportData(source, d->connection); + return new KexiDBReportData(source, d->connection, d->view); } diff --git a/kexi/plugins/reports/kexidbreportdata.h b/kexi/plugins/reports/kexidbreportdata.h index 03af20a3ecd..4b9dcb95627 100644 --- a/kexi/plugins/reports/kexidbreportdata.h +++ b/kexi/plugins/reports/kexidbreportdata.h @@ -1,81 +1,84 @@ /* * Kexi Report Plugin * Copyright (C) 2007-2009 by Adam Pigg (adam@piggz.co.uk) +* Copyright (C) 2015 Jarosław Staniek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #ifndef __KEXIDBREPORTDATA_H__ #define __KEXIDBREPORTDATA_H__ #include #include #include #include #include +class KexiReportView; + /** */ class KexiDBReportData : public KoReportData { public: - KexiDBReportData(const QString &objectName, KexiDB::Connection *conn); + KexiDBReportData(const QString &objectName, KexiDB::Connection *conn, KexiReportView *view); /*! * @a partClass specifies @a objectName type: a table or query. * Types accepted: * -"org.kexi-project.table" * -"org.kexi-project.query" * -empty QString() - attempt to resolve @a objectName */ - KexiDBReportData(const QString &objectName, const QString& partClass, KexiDB::Connection *conn); + KexiDBReportData(const QString &objectName, const QString& partClass, KexiDB::Connection *conn, KexiReportView *view); virtual ~KexiDBReportData(); virtual QStringList fieldNames() const; virtual void setSorting(const QList& sorting); virtual void addExpression(const QString &field, const QVariant &value, int relation = '='); virtual QString sourceName() const; virtual int fieldNumber(const QString &field) const; virtual QVariant value(unsigned int) const; virtual QVariant value(const QString &field) const; virtual bool open(); virtual bool close(); virtual bool moveNext(); virtual bool movePrevious(); virtual bool moveFirst(); virtual bool moveLast(); virtual qint64 at() const; virtual qint64 recordCount() const; //Utility Functions virtual QStringList scriptList(const QString& language) const; virtual QString scriptCode(const QString& script, const QString& language) const; virtual QStringList dataSources() const; virtual KoReportData* create(const QString &source); private: class Private; Private * const d; bool getSchema(const QString& partClass = QString()); }; #endif diff --git a/kexi/plugins/reports/kexireportview.cpp b/kexi/plugins/reports/kexireportview.cpp index a2fefd3a44b..b18ec9dfd6b 100644 --- a/kexi/plugins/reports/kexireportview.cpp +++ b/kexi/plugins/reports/kexireportview.cpp @@ -1,532 +1,529 @@ /* * Kexi Report Plugin * Copyright (C) 2007-2008 by Adam Pigg (adam@piggz.co.uk) Copyright (C) 2014 Jarosław Staniek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "kexireportview.h" #include #include "kexidbreportdata.h" #ifndef KEXI_MOBILE #include "keximigratereportdata.h" #endif #include #include #include #include #include #include #include #include #include #include #include "krscriptfunctions.h" #include #include #include #include #include #include #include #ifndef KEXI_MOBILE #include #endif #include #include #include "../scripting/kexiscripting/kexiscriptadaptor.h" KexiReportView::KexiReportView(QWidget *parent) - : KexiView(parent), m_preRenderer(0), m_reportDocument(0), m_currentPage(0), m_pageCount(0), m_kexi(0), m_functions(0) + : KexiView(parent), m_preRenderer(0), m_currentPage(0), m_pageCount(0), m_kexi(0), m_functions(0) { setObjectName("KexiReportDesigner_DataView"); m_reportView = new QGraphicsView(this); // page selector should be always visible: m_reportView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); layout()->addWidget(m_reportView); m_reportScene = new QGraphicsScene(this); m_reportScene->setSceneRect(0,0,1000,2000); m_reportView->setScene(m_reportScene); m_reportScene->setBackgroundBrush(palette().brush(QPalette::Dark)); #ifndef KEXI_MOBILE m_pageSelector = new KexiRecordNavigator(*m_reportView, m_reportView); m_pageSelector->setInsertingButtonVisible(false); m_pageSelector->setInsertingEnabled(false); m_pageSelector->setLabelText(i18n("Page")); m_pageSelector->setButtonToolTipText(KexiRecordNavigator::ButtonFirst, i18n("Go to first page")); m_pageSelector->setButtonWhatsThisText(KexiRecordNavigator::ButtonFirst, i18n("Goes to first page")); m_pageSelector->setButtonToolTipText(KexiRecordNavigator::ButtonPrevious, i18n("Go to previous page")); m_pageSelector->setButtonWhatsThisText(KexiRecordNavigator::ButtonPrevious, i18n("Goes to previous page")); m_pageSelector->setButtonToolTipText(KexiRecordNavigator::ButtonNext, i18n("Go to next page")); m_pageSelector->setButtonWhatsThisText(KexiRecordNavigator::ButtonNext, i18n("Goes to next page")); m_pageSelector->setButtonToolTipText(KexiRecordNavigator::ButtonLast, i18n("Go to last page")); m_pageSelector->setButtonWhatsThisText(KexiRecordNavigator::ButtonLast, i18n("Goes to last page")); m_pageSelector->setNumberFieldToolTips(i18n("Current page number"), i18n("Number of pages")); m_pageSelector->setRecordHandler(this); #endif // -- setup local actions QList viewActions; QAction* a; #ifndef KEXI_MOBILE viewActions << (a = new KAction(koIcon("document-print"), i18n("Print"), this)); a->setObjectName("print_report"); a->setToolTip(i18n("Print report")); a->setWhatsThis(i18n("Prints the current report.")); connect(a, SIGNAL(triggered()), this, SLOT(slotPrintReport())); KActionMenu *exportMenu = new KActionMenu(koIcon("document-export"), i18nc("@title:menu","E&xport As"), this); exportMenu->setObjectName("report_export_as"); exportMenu->setDelayed(false); #endif #ifdef KEXI_MOBILE viewActions << (a = new KAction(i18n("Export:"), this)); a->setEnabled(false); //!TODO this is a bit of a dirty way to add what looks like a label to the toolbar! // " ", not "", is said to be needed in maemo, the icon didn't display properly without it viewActions << (a = new KAction(koIcon("application-vnd.oasis.opendocument.text"), QLatin1String(" "), this)); #else exportMenu->addAction(a = new KAction(koIcon("application-vnd.oasis.opendocument.text"), i18nc("open dialog to export as text document", "Text Document..."), this)); #endif a->setObjectName("export_as_text_document"); a->setToolTip(i18n("Export the report as a text document (in OpenDocument Text format)")); a->setWhatsThis(i18n("Exports the report as a text document (in OpenDocument Text format).")); a->setEnabled(true); connect(a, SIGNAL(triggered()), this, SLOT(slotExportAsTextDocument())); #ifdef KEXI_MOBILE viewActions << (a = new KAction(koIcon("application-pdf"), QLatin1String(" "), this)); #else exportMenu->addAction(a = new KAction(koIcon("application-pdf"), i18nc("Portable Document Format...", "PDF..."), this)); #endif a->setObjectName("export_as_pdf"); a->setToolTip(i18n("Export as PDF")); a->setWhatsThis(i18n("Exports the current report as PDF.")); a->setEnabled(true); connect(a, SIGNAL(triggered()), this, SLOT(slotExportAsPdf())); #ifdef KEXI_MOBILE viewActions << (a = new KAction(koIcon("application-vnd.oasis.opendocument.spreadsheet"), QLatin1String(" "), this)); #else exportMenu->addAction(a = new KAction(koIcon("application-vnd.oasis.opendocument.spreadsheet"), i18nc("open dialog to export as spreadsheet", "Spreadsheet..."), this)); #endif a->setObjectName("export_as_spreadsheet"); a->setToolTip(i18n("Export the report as a spreadsheet (in OpenDocument Spreadsheet format)")); a->setWhatsThis(i18n("Exports the report as a spreadsheet (in OpenDocument Spreadsheet format).")); a->setEnabled(true); connect(a, SIGNAL(triggered()), this, SLOT(slotExportAsSpreadsheet())); #ifdef KEXI_MOBILE viewActions << (a = new KAction(koIcon("text-html"), QLatin1String(" "), this)); #else exportMenu->addAction(a = new KAction(koIcon("text-html"), i18nc("open dialog to export as web page", "Web Page..."), this)); #endif a->setObjectName("export_as_web_page"); a->setToolTip(i18n("Export the report as a web page (in HTML format)")); a->setWhatsThis(i18n("Exports the report as a web page (in HTML format).")); a->setEnabled(true); connect(a, SIGNAL(triggered()), this, SLOT(slotExportAsWebPage())); setViewActions(viewActions); #ifndef KEXI_MOBILE // setup main menu actions QList mainMenuActions; mainMenuActions << exportMenu; setMainMenuActions(mainMenuActions); connect(m_pageSelector, SIGNAL(nextButtonClicked()), this, SLOT(nextPage())); connect(m_pageSelector, SIGNAL(prevButtonClicked()), this, SLOT(prevPage())); connect(m_pageSelector, SIGNAL(firstButtonClicked()), this, SLOT(firstPage())); connect(m_pageSelector, SIGNAL(lastButtonClicked()), this, SLOT(lastPage())); #endif } KexiReportView::~KexiReportView() { kDebug(); delete m_preRenderer; delete m_kexi; delete m_functions; - delete m_reportDocument; } void KexiReportView::slotPrintReport() { QPrinter printer(QPrinter::HighResolution); QPainter painter; KoReportRendererBase *renderer; renderer = m_factory.createInstance("print"); QPointer dialog = new QPrintDialog(&printer, this); if (dialog->exec() == QDialog::Accepted) { KoReportRendererContext cxt; cxt.printer = &printer; cxt.painter = &painter; - renderer->render(cxt, m_reportDocument); + renderer->render(cxt, m_preRenderer->document()); } delete dialog; delete renderer; } void KexiReportView::slotExportAsPdf() { QScopedPointer renderer(m_factory.createInstance("print")); if (renderer) { KoReportRendererContext cxt; cxt.destinationUrl = getExportUrl(QLatin1String("application/pdf"), i18n("Export Report as PDF"), "kfiledialog:///LastVisitedPDFExportPath/", "pdf"); if (!cxt.destinationUrl.isValid()) { return; } QPrinter printer; QPainter painter; printer.setOutputFileName(cxt.destinationUrl.path()); printer.setOutputFormat(QPrinter::PdfFormat); printer.setColorMode(QPrinter::Color); painter.begin(&printer); cxt.printer = &printer; cxt.painter = &painter; - if (!renderer->render(cxt, m_reportDocument)) { + if (!renderer->render(cxt, m_preRenderer->document())) { KMessageBox::error(this, i18n("Exporting the report as PDF to %1 failed.", cxt.destinationUrl.prettyUrl()), i18n("Export Failed")); } else { openExportedDocument(cxt.destinationUrl); } } } KUrl KexiReportView::getExportUrl(const QString &mimetype, const QString &caption, const QString &lastExportPath, const QString &extension) { KUrl result; QString defaultSavePath; if (lastExportPath.startsWith("kfiledialog:///")) { defaultSavePath = lastExportPath + window()->partItem()->captionOrName() + "." + extension; } // loop until an url has been chosen or the file selection has been cancelled while (true) { result = KFileDialog::getSaveUrl(KUrl(defaultSavePath), mimetype, this, caption); // not cancelled? if (result.isValid()) { if (KIO::NetAccess::exists(result, KIO::NetAccess::DestinationSide, this)) { const int answer = KMessageBox::warningContinueCancel(this, i18n("The file %1 exists.\nDo you want to overwrite it?", result.path()), caption, KGuiItem(i18n("Overwrite"))); // if overwriting not wanted, let select another url if (answer == KMessageBox::Cancel) { continue; } } } // decision has been made, leave loop break; } return result; } void KexiReportView::openExportedDocument(const KUrl& destination) { const int answer = KMessageBox::questionYesNo( this, i18n("Do you want to open exported document?"), QString(), KStandardGuiItem::open(), KStandardGuiItem::close()); if (answer == KMessageBox::Yes) { (void)new KRun(destination, this->topLevelWidget()); } } void KexiReportView::slotExportAsSpreadsheet() { KoReportRendererBase *renderer; KoReportRendererContext cxt; renderer = m_factory.createInstance("ods"); if (renderer) { cxt.destinationUrl = getExportUrl(QLatin1String("application/vnd.oasis.opendocument.spreadsheet"), i18n("Export Report as Spreadsheet"), "kfiledialog:///LastVisitedODSExportPath/", "ods"); if (!cxt.destinationUrl.isValid()) { return; } - if (!renderer->render(cxt, m_reportDocument)) { + if (!renderer->render(cxt, m_preRenderer->document())) { KMessageBox::error(this, i18n("Failed to export the report as spreadsheet to %1.", cxt.destinationUrl.prettyUrl()), i18n("Export Failed")); } else { openExportedDocument(cxt.destinationUrl); } } } void KexiReportView::slotExportAsTextDocument() { KoReportRendererBase *renderer; KoReportRendererContext cxt; renderer = m_factory.createInstance("odt"); if (renderer) { cxt.destinationUrl = getExportUrl(QLatin1String("application/vnd.oasis.opendocument.text"), i18n("Export Report as Text Document"), "kfiledialog:///LastVisitedODTExportPath/", "odt"); if (!cxt.destinationUrl.isValid()) { return; } - if (!renderer->render(cxt, m_reportDocument)) { + if (!renderer->render(cxt, m_preRenderer->document())) { KMessageBox::error(this, i18n("Exporting the report as text document to %1 failed.", cxt.destinationUrl.prettyUrl()), i18n("Export Failed")); } else { openExportedDocument(cxt.destinationUrl); } } } void KexiReportView::slotExportAsWebPage() { KoReportRendererContext cxt; KoReportRendererBase *renderer; const QString dialogTitle = i18n("Export Report as Web Page"); cxt.destinationUrl = getExportUrl(QLatin1String("text/html"), dialogTitle, "kfiledialog:///LastVisitedHTMLExportPath/", "html"); if (!cxt.destinationUrl.isValid()) { return; } const int answer = KMessageBox::questionYesNo( this, i18n("Would you like to export using a Cascading Style Sheet (CSS), " "which will give an output closer to the original, " "or export using a HTML Table, which outputs a much simpler format?"), dialogTitle, KGuiItem(i18n("Use CSS")), KGuiItem(i18n("Use Table"))); if (answer == KMessageBox::Yes) { renderer = m_factory.createInstance("htmlcss"); } else { renderer = m_factory.createInstance("htmltable"); } - if (!renderer->render(cxt, m_reportDocument)) { + if (!renderer->render(cxt, m_preRenderer->document())) { KMessageBox::error(this, i18n("Exporting the report as web page to %1 failed.", cxt.destinationUrl.prettyUrl()), i18n("Export Failed")); } else { openExportedDocument(cxt.destinationUrl); } } tristate KexiReportView::beforeSwitchTo(Kexi::ViewMode mode, bool &dontStore) { Q_UNUSED(mode); Q_UNUSED(dontStore); return true; } tristate KexiReportView::afterSwitchFrom(Kexi::ViewMode mode) { Q_UNUSED(mode); if (tempData()->reportSchemaChangedInPreviousView) { + tempData()->reportSchemaChangedInPreviousView = false; + kDebug() << "Schema changed"; delete m_preRenderer; //kDebug() << tempData()->reportDefinition.tagName(); m_preRenderer = new KoReportPreRenderer(tempData()->reportDefinition); if (m_preRenderer->isValid()) { KoReportData *reportData = 0; if (!tempData()->connectionDefinition.isNull()) { reportData = sourceData(tempData()->connectionDefinition); } if (!reportData) { - reportData = new KexiDBReportData(QString(), KexiMainWindowIface::global()->project()->dbConnection()); + reportData = new KexiDBReportData(QString(), KexiMainWindowIface::global()->project()->dbConnection(), this); } m_preRenderer->setSourceData(reportData); m_preRenderer->setName(tempData()->name); m_currentPage = 1; //Add a kexi object to provide kexidb and extra functionality if(!m_kexi) { m_kexi = new KexiScriptAdaptor(); } m_preRenderer->registerScriptObject(m_kexi, "Kexi"); //If using a kexidb source, add a functions scripting object if (tempData()->connectionDefinition.attribute("type") == "internal") { delete m_functions; // prev functions m_functions = new KRScriptFunctions(reportData, KexiMainWindowIface::global()->project()->dbConnection()); m_preRenderer->registerScriptObject(m_functions, "field"); } - delete m_reportDocument; // prev document - m_reportDocument = m_preRenderer->generate(); - if (m_reportDocument) { - m_pageCount = m_reportDocument->pages(); + if (!m_preRenderer->generateDocument()) { + return false; + } + m_pageCount = m_preRenderer->document()->pages(); #ifndef KEXI_MOBILE - m_pageSelector->setRecordCount(m_pageCount); - m_pageSelector->setCurrentRecordNumber(1); + m_pageSelector->setRecordCount(m_pageCount); + m_pageSelector->setCurrentRecordNumber(1); #endif - } - - m_reportPage = new KoReportPage(this, m_reportDocument); + m_reportPage = new KoReportPage(this, m_preRenderer->document()); m_reportPage->setObjectName("KexiReportPage"); m_reportScene->setSceneRect(0,0,m_reportPage->rect().width() + 40, m_reportPage->rect().height() + 40); m_reportScene->addItem(m_reportPage); m_reportPage->setPos(20,20); m_reportView->centerOn(0,0); - } else { KMessageBox::error(this, i18n("Report schema appears to be invalid or corrupt"), i18n("Opening failed")); } - - - tempData()->reportSchemaChangedInPreviousView = false; } return true; } KoReportData* KexiReportView::sourceData(QDomElement e) { KoReportData *kodata = 0; if (e.attribute("type") == "internal") { - kodata = new KexiDBReportData(e.attribute("source"), KexiMainWindowIface::global()->project()->dbConnection()); + kodata = new KexiDBReportData(e.attribute("source"), + KexiMainWindowIface::global()->project()->dbConnection(), + this); } #ifndef KEXI_MOBILE if (e.attribute("type") == "external") { kodata = new KexiMigrateReportData(e.attribute("source")); } #endif return kodata; } KexiReportPart::TempData* KexiReportView::tempData() const { return static_cast(window()->data()); } void KexiReportView::addNewRecordRequested() { } void KexiReportView::moveToFirstRecordRequested() { if (m_currentPage != 1) { m_currentPage = 1; m_reportPage->renderPage(m_currentPage); #ifndef KEXI_MOBILE m_pageSelector->setCurrentRecordNumber(m_currentPage); #endif } } void KexiReportView::moveToLastRecordRequested() { if (m_currentPage != m_pageCount) { m_currentPage = m_pageCount; m_reportPage->renderPage(m_currentPage); #ifndef KEXI_MOBILE m_pageSelector->setCurrentRecordNumber(m_currentPage); #endif } } void KexiReportView::moveToNextRecordRequested() { if (m_currentPage < m_pageCount) { m_currentPage++; m_reportPage->renderPage(m_currentPage); #ifndef KEXI_MOBILE m_pageSelector->setCurrentRecordNumber(m_currentPage); #endif } } void KexiReportView::moveToPreviousRecordRequested() { if (m_currentPage > 1) { m_currentPage--; m_reportPage->renderPage(m_currentPage); #ifndef KEXI_MOBILE m_pageSelector->setCurrentRecordNumber(m_currentPage); #endif } } void KexiReportView::moveToRecordRequested(uint r) { Q_UNUSED(r); } int KexiReportView::currentRecord() const { return m_currentPage; } int KexiReportView::recordCount() const { return m_pageCount; } diff --git a/kexi/plugins/reports/kexireportview.h b/kexi/plugins/reports/kexireportview.h index c5c52ad913a..7263e8dfd13 100644 --- a/kexi/plugins/reports/kexireportview.h +++ b/kexi/plugins/reports/kexireportview.h @@ -1,94 +1,93 @@ /* * Kexi Report Plugin * Copyright (C) 2007-2008 by Adam Pigg (adam@piggz.co.uk) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #ifndef KEXIREPORTVIEW_H #define KEXIREPORTVIEW_H #include #include #include #include "kexireportpart.h" #include #include class KoReportPreRenderer; class ORODocument; class KoReportPage; class KexiScriptAdaptor; class KRScriptFunctions; #ifndef KEXI_MOBILE class KexiRecordNavigator; #endif #include /** @author Adam Pigg */ class KexiReportView : public KexiView, public KexiRecordNavigatorHandler { Q_OBJECT public: explicit KexiReportView(QWidget *parent); ~KexiReportView(); virtual tristate afterSwitchFrom(Kexi::ViewMode mode); virtual tristate beforeSwitchTo(Kexi::ViewMode mode, bool &dontStore); virtual void addNewRecordRequested(); virtual void moveToFirstRecordRequested(); virtual void moveToLastRecordRequested(); virtual void moveToNextRecordRequested(); virtual void moveToPreviousRecordRequested(); virtual void moveToRecordRequested(uint r); virtual int currentRecord() const; virtual int recordCount() const; private: KoReportPreRenderer *m_preRenderer; - ORODocument *m_reportDocument; QGraphicsView *m_reportView; QGraphicsScene *m_reportScene; KoReportPage *m_reportPage; #ifndef KEXI_MOBILE KexiRecordNavigator *m_pageSelector; #endif int m_currentPage; int m_pageCount; KexiReportPart::TempData* tempData() const; KoReportData* sourceData(QDomElement e); KexiScriptAdaptor *m_kexi; KRScriptFunctions *m_functions; KoReportRendererFactory m_factory; KUrl getExportUrl(const QString &mimetype, const QString &caption, const QString &lastExportPath, const QString &extension); private Q_SLOTS: void slotPrintReport(); void slotExportAsPdf(); void slotExportAsSpreadsheet(); void slotExportAsWebPage(); void slotExportAsTextDocument(); void openExportedDocument(const KUrl& destination); }; #endif diff --git a/kexi/plugins/reports/kexisourceselector.cpp b/kexi/plugins/reports/kexisourceselector.cpp index b44348a2011..ba8281b350d 100644 --- a/kexi/plugins/reports/kexisourceselector.cpp +++ b/kexi/plugins/reports/kexisourceselector.cpp @@ -1,205 +1,217 @@ /* * Kexi Report Plugin * Copyright (C) 2007-2009 by Adam Pigg (adam@piggz.co.uk) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "kexisourceselector.h" +#include "kexireportview.h" +#include +#include "KexiDataSourceComboBox.h" +#include +#include #include #include - -#include #include #include + #include -#include "KexiDataSourceComboBox.h" -#include +#include //#define NO_EXTERNAL_SOURCES #ifdef NO_EXTERNAL_SOURCES #ifdef __GNUC__ #warning enable external data sources for 2.3 #else #pragma WARNING( enable external data sources for 2.3 ) #endif #endif class KexiSourceSelector::Private { public: Private() : kexiDBData(0) { } ~Private() { delete kexiDBData; #ifndef KEXI_MOBILE delete kexiMigrateData; #endif } KexiDB::Connection *conn; QVBoxLayout *layout; QComboBox *sourceType; KexiDataSourceComboBox *internalSource; KLineEdit *externalSource; KPushButton *setData; KexiDBReportData *kexiDBData; #ifndef KEXI_MOBILE KexiMigrateReportData *kexiMigrateData; #endif }; KexiSourceSelector::KexiSourceSelector(KexiProject* project, QWidget* parent) : QWidget(parent) , d(new Private) { d->conn = project->dbConnection(); d->kexiDBData = 0; #ifndef KEXI_MOBILE d->kexiMigrateData = 0; #endif d->layout = new QVBoxLayout(this); d->sourceType = new QComboBox(this); d->internalSource = new KexiDataSourceComboBox(this); d->internalSource->setProject(project); d->externalSource = new KLineEdit(this); d->setData = new KPushButton(i18n("Set Data")); connect(d->setData, SIGNAL(clicked()), this, SLOT(setDataClicked())); d->sourceType->addItem(i18n("Internal"), QVariant("internal")); d->sourceType->addItem(i18n("External"), QVariant("external")); #ifndef NO_EXTERNAL_SOURCES //!@TODO enable when adding external data d->layout->addWidget(new QLabel(i18n("Source Type:"), this)); d->layout->addWidget(d->sourceType); d->layout->addSpacing(10); #else d->sourceType->setVisible(false); d->externalSource->setVisible(false); #endif d->layout->addWidget(new QLabel(i18n("Internal Source:"), this)); d->layout->addWidget(d->internalSource); d->layout->addSpacing(10); #ifndef NO_EXTERNAL_SOURCES d->layout->addWidget(new QLabel(i18n("External Source:"), this)); d->layout->addWidget(d->externalSource); #endif d->layout->addSpacing(20); d->layout->addWidget(d->setData); d->layout->addStretch(); setLayout(d->layout); } KexiSourceSelector::~KexiSourceSelector() { delete d; } void KexiSourceSelector::setConnectionData(const QDomElement &c) { if (c.attribute("type") == "internal") { d->sourceType->setCurrentIndex(d->sourceType->findData("internal")); d->internalSource->setCurrentIndex(d->internalSource->findText(c.attribute("source"))); } if (c.attribute("type") == "external") { d->sourceType->setCurrentIndex(d->sourceType->findText("external")); d->externalSource->setText(c.attribute("source")); } emit setData(sourceData()); } QDomElement KexiSourceSelector::connectionData() { kDebug(); QDomDocument dd; QDomElement conndata = dd.createElement("connection"); #ifndef NO_EXTERNAL_SOURCES //!@TODO Make a better gui for selecting external data source conndata.setAttribute("type", d->sourceType->itemData(d->sourceType->currentIndex()).toString()); if (d->sourceType->itemData(d->sourceType->currentIndex()).toString() == "internal") { conndata.setAttribute("source", d->internalSource->currentText()); } else { conndata.setAttribute("source", d->externalSource->text()); } #else conndata.setAttribute("type", "internal"); conndata.setAttribute("source", d->internalSource->currentText()); #endif return conndata; } KoReportData* KexiSourceSelector::sourceData() { if (d->kexiDBData) { delete d->kexiDBData; d->kexiDBData = 0; } #ifndef KEXI_MOBILE if (d->kexiMigrateData) { delete d->kexiMigrateData; d->kexiMigrateData = 0; } #endif //!@TODO Fix when enable external data #ifndef NO_EXTERNAL_SOURCES + KexiReportView *view = 0; + if (KexiMainWindowIface::global()->currentWindow()) { + view = qobject_cast(KexiMainWindowIface::global()->currentWindow()->selectedView()); + if (!view) { + return 0; + } + } if (d->sourceType->itemData(d->sourceType->currentIndex()).toString() == "internal" && d->internalSource->isSelectionValid()) { - d->kexiDBData = new KexiDBReportData(d->internalSource->selectedName(), d->internalSource->selectedPartClass(), d->conn); + d->kexiDBData = new KexiDBReportData(d->internalSource->selectedName(), + d->internalSource->selectedPartClass(), + d->conn, view); return d->kexiDBData; } #ifndef KEXI_MOBILE if (d->sourceType->itemData(d->sourceType->currentIndex()).toString() == "external") { d->kexiMigrateData = new KexiMigrateReportData(d->externalSource->text()); return d->kexiMigrateData; } #endif #else if (d->internalSource->isSelectionValid()) { d->kexiDBData = new KexiDBReportData(d->internalSource->selectedName(), d->conn); return d->kexiDBData; } #endif return 0; } void KexiSourceSelector::setDataClicked() { emit(setData(sourceData())); } diff --git a/kexi/plugins/tables/kexitabledesigner_dataview.cpp b/kexi/plugins/tables/kexitabledesigner_dataview.cpp index ed9985ed852..b17f1319e6b 100644 --- a/kexi/plugins/tables/kexitabledesigner_dataview.cpp +++ b/kexi/plugins/tables/kexitabledesigner_dataview.cpp @@ -1,95 +1,98 @@ /* This file is part of the KDE project Copyright (C) 2004-2014 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kexitabledesigner_dataview.h" #include #include #include #include #include #include #include KexiTableDesigner_DataView::KexiTableDesigner_DataView(QWidget *parent) : KexiDataTableView(parent, true/*db-aware*/) { setObjectName("KexiTableDesigner_DataView"); // setup main menu actions QList mainMenuActions; mainMenuActions << sharedAction("project_export_data_table") << sharedAction("edit_clear_table"); setMainMenuActions(mainMenuActions); } KexiTableDesigner_DataView::~KexiTableDesigner_DataView() { #ifdef __GNUC__ #warning TODO crash #else #pragma WARNING( TODO crash ) #endif /*TODO if (dynamic_cast(tableView()) && dynamic_cast(tableView())->cursor()) { KexiMainWindowIface::global()->project()->dbConnection()->deleteCursor( dynamic_cast(tableView())->cursor() ); }*/ } tristate KexiTableDesigner_DataView::beforeSwitchTo(Kexi::ViewMode mode, bool &dontStore) { Q_UNUSED(dontStore); if (mode != Kexi::DataViewMode) { //accept editing before switching if (!acceptRowEdit()) { return cancelled; } } return true; } tristate KexiTableDesigner_DataView::afterSwitchFrom(Kexi::ViewMode mode) { Q_UNUSED(mode); if (tempData()->tableSchemaChangedInPreviousView) { KexiUtils::WaitCursor wait; - KexiDB::Cursor *c - = KexiMainWindowIface::global()->project()->dbConnection()->prepareQuery( - *tempData()->table); - if (!c) + KexiDB::Connection *conn = KexiMainWindowIface::global()->project()->dbConnection(); + KexiDB::Cursor *cursor = conn->prepareQuery(*tempData()->table); + if (!cursor) { return false; - setData(c); + } + if (!setData(cursor)) { + conn->deleteCursor(cursor); + return false; + } tempData()->tableSchemaChangedInPreviousView = false; } return true; } KexiTablePart::TempData* KexiTableDesigner_DataView::tempData() const { return static_cast(window()->data()); } #include "kexitabledesigner_dataview.moc" diff --git a/kexi/widget/tableview/KexiDataTableScrollArea.cpp b/kexi/widget/tableview/KexiDataTableScrollArea.cpp index 417eacee47d..b5817d83a67 100644 --- a/kexi/widget/tableview/KexiDataTableScrollArea.cpp +++ b/kexi/widget/tableview/KexiDataTableScrollArea.cpp @@ -1,107 +1,112 @@ /* This file is part of the KDE project Copyright (C) 2003 Lucijan Busch Copyright (C) 2003 Joseph Wenninger Copyright (C) 2003-2014 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KexiDataTableScrollArea.h" #include #include #include #include #include "KexiDataTableScrollArea.h" #include "KexiDataTableView.h" KexiDataTableScrollArea::KexiDataTableScrollArea(QWidget *parent) : KexiTableScrollArea(0, parent) { init(); } KexiDataTableScrollArea::KexiDataTableScrollArea(QWidget *parent, KexiDB::Cursor *cursor) : KexiTableScrollArea(0, parent) { init(); setData(cursor); } KexiDataTableScrollArea::~KexiDataTableScrollArea() { } void KexiDataTableScrollArea::init() { m_cursor = 0; } bool KexiDataTableScrollArea::setData(KexiDB::Cursor *cursor) { if (!cursor) { clearColumns(); m_cursor = 0; return true; } if (cursor != m_cursor) { clearColumns(); } m_cursor = cursor; if (!m_cursor->query()) { kWarning() << "Cursor should have query schema defined!\n--aborting setData().\n"; m_cursor->debug(); clearColumns(); + m_cursor = 0; return false; } if (m_cursor->fieldCount() < 1) { clearColumns(); return true; } if (!m_cursor->isOpened() && !m_cursor->open()) { kWarning() << "Cannot open cursor\n--aborting setData(). \n" << m_cursor->serverErrorMsg(); m_cursor->debug(); clearColumns(); + m_cursor = 0; return false; } KexiDB::TableViewData *tv_data = new KexiDB::TableViewData(m_cursor); KexiDataTableView* dataTable = qobject_cast(parentWidget()); if (dataTable) { dataTable->loadTableViewSettings(tv_data); } QString windowTitle(m_cursor->query()->caption()); if (windowTitle.isEmpty()) windowTitle = m_cursor->query()->name(); setWindowTitle(windowTitle); //PRIMITIVE!! data setting: - tv_data->preloadAllRows(); - + if (!tv_data->preloadAllRows()) { + delete tv_data; + clearColumns(); + m_cursor = 0; + } KexiTableScrollArea::setData(tv_data); return true; } #include "KexiDataTableScrollArea.moc" diff --git a/kexi/widget/tableview/KexiDataTableView.cpp b/kexi/widget/tableview/KexiDataTableView.cpp index 10fba14eb99..e75ac202789 100644 --- a/kexi/widget/tableview/KexiDataTableView.cpp +++ b/kexi/widget/tableview/KexiDataTableView.cpp @@ -1,162 +1,163 @@ /* This file is part of the KDE project Copyright (C) 2003 Lucijan Busch Copyright (C) 2003 Joseph Wenninger Copyright (C) 2003-2014 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include "KexiDataTableScrollArea.h" #include "KexiDataTableView.h" #include #include #include class KexiDataTableView::Private { public: bool storeUserDataBlock(int objectID, const QString& dataID, const QString &dataString, KexiDB::TransactionGuard *tg) { if (transaction.isNull()) { transaction = KexiMainWindowIface::global()->project()->dbConnection()->beginTransaction(); tg->setTransaction(transaction); } return KexiMainWindowIface::global()->project()->storeUserDataBlock( objectID, dataID, dataString); } KexiDB::Transaction transaction; }; KexiDataTableView::KexiDataTableView(QWidget *parent, bool dbAware) : KexiDataAwareView(parent) , d(new Private) { KexiTableScrollArea *view; if (dbAware) view = new KexiDataTableScrollArea(this); else view = new KexiTableScrollArea(0, this); view->setObjectName("datatableview"); KexiDataAwareView::init(view, view, view); } KexiDataTableView::KexiDataTableView(QWidget *parent, KexiDB::Cursor *cursor) : KexiDataAwareView(parent) , d(new Private) { KexiTableScrollArea *view = new KexiDataTableScrollArea(this, cursor); KexiDataAwareView::init(view, view, view); } KexiDataTableView::~KexiDataTableView() { delete d; } bool KexiDataTableView::loadTableViewSettings(KexiDB::TableViewData* data) { Q_ASSERT(data); const int id = window()->id(); if (id > 0 && data->columnCount() > 0) { QString columnWidthsString; tristate res = KexiMainWindowIface::global()->project()->loadUserDataBlock( id, "columnWidths", &columnWidthsString); if (false == res) { return false; } else if (true == res) { bool ok; const QList columnWidths = KexiDB::deserializeIntList(columnWidthsString, &ok); if (!ok) { kWarning() << "Invalud format of 'columnWidths' value:" << columnWidthsString; return false; } KexiDB::TableViewColumn::List* columns = data->columns(); if (columnWidths.count() == columns->count()) { int i = 0; foreach (int width, columnWidths) { // kDebug() << width; columns->at(i)->setWidth(width); ++i; } } } } return true; } -void -KexiDataTableView::setData(KexiDB::Cursor *c) +bool KexiDataTableView::setWidgetData(KexiDB::Cursor *cursor) { - if (!dynamic_cast(mainWidget())) - return; - dynamic_cast(mainWidget())->setData(c); + bool ok = dynamic_cast(mainWidget()); + if (!ok) { + return false; + } + return dynamic_cast(mainWidget())->setData(cursor); } void KexiDataTableView::filter() { } KexiTableScrollArea* KexiDataTableView::tableView() const { return dynamic_cast(internalView()); } bool KexiDataTableView::saveSettings() { #ifdef __GNUC__ #warning TODO save only if changed #else #pragma WARNING(TODO save only if changed) #endif bool ok = true; KexiDB::TransactionGuard tg; if (dynamic_cast(mainWidget())) { // db-aware KexiTableScrollArea* tv = tableView(); const int id = window()->id(); if (id > 0 && tv->data()->columnCount() > 0) { QStringList widths; bool equal = true; // will be only saved if widths are not equal for (uint i = 0; i < tv->data()->columnCount(); ++i) { if (equal) { equal = tv->data()->column(i)->width() == uint(tv->columnWidth(i)); } widths.append(QString::number(tv->columnWidth(i))); } if ( !equal && !d->storeUserDataBlock(id, "columnWidths", KexiDB::variantToString(widths), &tg)) { return false; } } ok = tg.commit(); } return ok; } #include "KexiDataTableView.moc" diff --git a/kexi/widget/tableview/KexiDataTableView.h b/kexi/widget/tableview/KexiDataTableView.h index 8c1c964c21a..37f21174f91 100644 --- a/kexi/widget/tableview/KexiDataTableView.h +++ b/kexi/widget/tableview/KexiDataTableView.h @@ -1,86 +1,85 @@ /* This file is part of the KDE project Copyright (C) 2003 Lucijan Busch Copyright (C) 2003 Joseph Wenninger Copyright (C) 2003-2014 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KEXIDATATABLE_H #define KEXIDATATABLE_H #include class KexiTableScrollArea; namespace KexiDB { class Cursor; class TableViewData; } /*! @short Provides a data-driven (record-based) tabular view. The KexiDataTableView can display data provided "by hand" or from KexiDB-compatible database source. @see KexiFormView */ class KEXIDATATABLE_EXPORT KexiDataTableView : public KexiDataAwareView { Q_OBJECT public: /*! CTOR1: Creates, empty table view that can be initialized later with setData(). If \a dbAware is true, table will be db-aware, and KexiDataTableView is used internally. Otherwise, table will be not-db-aware, and KexiTableView is used internally. In the latter case, data can be set by calling tableView()->setData(KexiDB::TableViewData* data). */ explicit KexiDataTableView(QWidget *parent, bool dbAware = true); /*! CTOR2: Creates db-aware, table view initialized with \a cursor. KexiDataTableView is used internally. */ KexiDataTableView(QWidget *parent, KexiDB::Cursor *cursor); virtual ~KexiDataTableView(); KexiTableScrollArea* tableView() const; //! Loads settings for table into @a data model. //! Used after loading data model in KexiDataTableView::setData(KexiDB::Cursor*), before calling KexiTableView::setData(). //! @return true on success bool loadTableViewSettings(KexiDB::TableViewData* data); public Q_SLOTS: - /*! Sets data. Only works for db-aware table. */ - void setData(KexiDB::Cursor *cursor); - /*! Saves settings for the view. Implemented for KexiView. */ virtual bool saveSettings(); protected Q_SLOTS: //! @todo void filter(); protected: void init(); + virtual bool setWidgetData(KexiDB::Cursor *cursor); + class Private; Private * const d; }; #endif diff --git a/libs/koreport/renderer/KoReportPage.cpp b/libs/koreport/renderer/KoReportPage.cpp index 65bc1be9c94..645165a94d2 100644 --- a/libs/koreport/renderer/KoReportPage.cpp +++ b/libs/koreport/renderer/KoReportPage.cpp @@ -1,111 +1,105 @@ /* * Kexi Report Plugin * Copyright (C) 2007-2008 by Adam Pigg (adam@piggz.co.uk) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "KoReportPage.h" #include #include #include #include #include #include #include #include #include KoReportPage::KoReportPage(QWidget *parent, ORODocument *document) : QObject(parent), QGraphicsRectItem() + , m_reportDocument(document) + , m_page(0) { - //TODO setAttribute(Qt::WA_NoBackground); - //kDebug() << "CREATED PAGE"; - m_reportDocument = document; - m_page = 0; - int pageWidth = 0; - int pageHeight = 0; + Q_ASSERT(m_reportDocument); - if (m_reportDocument) { - QString pageSize = m_reportDocument->pageOptions().getPageSize(); - - - if (pageSize == "Custom") { - // if this is custom sized sheet of paper we will just use those values - pageWidth = (int)(m_reportDocument->pageOptions().getCustomWidth()); - pageHeight = (int)(m_reportDocument->pageOptions().getCustomHeight()); - } else { - // lookup the correct size information for the specified size paper - pageWidth = m_reportDocument->pageOptions().widthPx(); - pageHeight = m_reportDocument->pageOptions().heightPx(); - } + int pageWidth; + int pageHeight; + const QString pageSize = m_reportDocument->pageOptions().getPageSize(); + if (pageSize == "Custom") { + // if this is custom sized sheet of paper we will just use those values + pageWidth = (int)(m_reportDocument->pageOptions().getCustomWidth()); + pageHeight = (int)(m_reportDocument->pageOptions().getCustomHeight()); + } else { + // lookup the correct size information for the specified size paper + pageWidth = m_reportDocument->pageOptions().widthPx(); + pageHeight = m_reportDocument->pageOptions().heightPx(); } - setRect(0,0,pageWidth, pageHeight); - //kDebug() << "PAGE IS " << pageWidth << "x" << pageHeight; + setRect(0, 0, pageWidth, pageHeight); + m_pixmap = new QPixmap(pageWidth, pageHeight); m_renderer = m_factory.createInstance("screen"); connect(m_reportDocument, SIGNAL(updated(int)), this, SLOT(pageUpdated(int))); m_renderTimer = new QTimer(this); m_renderTimer->setSingleShot(true); connect(m_renderTimer, SIGNAL(timeout()), this, SLOT(renderCurrentPage())); - + renderPage(1); } KoReportPage::~KoReportPage() { delete m_renderer; delete m_pixmap; } void KoReportPage::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { Q_UNUSED(option); Q_UNUSED(widget); painter->drawPixmap(QPoint(0, 0), *m_pixmap); } void KoReportPage::renderPage(int page) { m_page = page - 1; m_pixmap->fill(); QPainter qp(m_pixmap); if (m_reportDocument) { KoReportRendererContext cxt; cxt.painter = &qp; m_renderer->render(cxt, m_reportDocument, m_page); } update(); } void KoReportPage::pageUpdated(int pageNo) { //kDebug() << pageNo << m_page; //Refresh this page if it changes if (pageNo == m_page) { //kDebug() << "Current page updated"; m_renderTimer->start(100); } } void KoReportPage::renderCurrentPage() { renderPage(m_page + 1); } - #include "KoReportPage.moc" diff --git a/libs/koreport/renderer/KoReportPreRenderer.cpp b/libs/koreport/renderer/KoReportPreRenderer.cpp index d3ee1166943..04f3b8836a7 100644 --- a/libs/koreport/renderer/KoReportPreRenderer.cpp +++ b/libs/koreport/renderer/KoReportPreRenderer.cpp @@ -1,692 +1,716 @@ /* * OpenRPT report writer and rendering engine * Copyright (C) 2001-2007 by OpenMFG, LLC (info@openmfg.com) * Copyright (C) 2007-2008 by Adam Pigg (adam@piggz.co.uk) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "KoReportPreRenderer.h" #include "renderobjects.h" #include "KoReportData.h" #include #include #include #include #include #include #include "scripting/krscripthandler.h" #include #include #include "KoReportASyncItemManager.h" // // KoReportPreRendererPrivate // This class is the private class that houses all the internal // variables so we can provide a cleaner interface to the user // without presenting to them things that they don't need to see // and may change over time. // class KoReportPreRendererPrivate : public QObject { Q_OBJECT public: KoReportPreRendererPrivate(); virtual ~KoReportPreRendererPrivate(); bool m_valid; ORODocument* m_document; OROPage* m_page; KoReportReportData* m_reportData; qreal m_yOffset; // how far down the current page are we qreal m_topMargin; // value stored in the correct units qreal m_bottomMargin; // -- same as above -- qreal m_leftMargin; // -- same as above -- qreal m_rightMargin; // -- same as above -- qreal m_maxHeight; // -- same as above -- qreal m_maxWidth; // -- same as above -- int m_pageCounter; // what page are we currently on? int m_recordCount; KoReportData* m_kodata; QList m_postProcText; void createNewPage(); qreal finishCurPage(bool = false); qreal finishCurPageSize(bool = false); void renderDetailSection(KRDetailSectionData &); qreal renderSection(const KRSectionData &); qreal renderSectionSize(const KRSectionData &); ///Scripting Stuff KRScriptHandler *m_scriptHandler; void initEngine(); + //! Generates m_document. Returns true on success. + //! @note m_document is not removed on failure, caller should remove it. + bool generateDocument(); + KoReportASyncItemManager* asyncManager; + QMap scriptObjects; private slots: void asyncItemsFinished(); signals: void enteredGroup(const QString&, const QVariant&); void exitedGroup(const QString&, const QVariant&); void renderingSection(KRSectionData*, OROPage*, QPointF); }; KoReportPreRendererPrivate::KoReportPreRendererPrivate() { m_valid = false; m_reportData = 0; m_document = 0; m_page = 0; m_yOffset = 0.0; m_topMargin = m_bottomMargin = 0.0; m_leftMargin = m_rightMargin = 0.0; m_pageCounter = 0; m_maxHeight = m_maxWidth = 0.0; m_kodata = 0; asyncManager = new KoReportASyncItemManager(this); connect(asyncManager, SIGNAL(finished()), this, SLOT(asyncItemsFinished())); } KoReportPreRendererPrivate::~KoReportPreRendererPrivate() { delete m_reportData; m_reportData = 0; m_postProcText.clear(); + delete m_document; } void KoReportPreRendererPrivate::createNewPage() { //kDebug(); if (m_pageCounter > 0) finishCurPage(); m_pageCounter++; //Update the page count script value m_scriptHandler->setPageNumber(m_pageCounter); m_scriptHandler->newPage(); m_page = new OROPage(0); m_document->addPage(m_page); //TODO calculate past page bool lastPage = false; m_yOffset = m_topMargin; if (m_pageCounter == 1 && m_reportData->m_pageHeaderFirst) renderSection(*(m_reportData->m_pageHeaderFirst)); else if (lastPage == true && m_reportData->m_pageHeaderLast) renderSection(*(m_reportData->m_pageHeaderLast)); else if ((m_pageCounter % 2) == 1 && m_reportData->m_pageHeaderOdd) renderSection(*(m_reportData->m_pageHeaderOdd)); else if ((m_pageCounter % 2) == 0 && m_reportData->m_pageHeaderAny) renderSection(*(m_reportData->m_pageHeaderAny)); else if (m_reportData->m_pageHeaderAny) renderSection(*(m_reportData->m_pageHeaderAny)); } qreal KoReportPreRendererPrivate::finishCurPageSize(bool lastPage) { qreal retval = 0.0; if (lastPage && m_reportData->m_pageFooterLast) retval = renderSectionSize(* (m_reportData->m_pageFooterLast)); else if (m_pageCounter == 1 && m_reportData->m_pageFooterFirst) retval = renderSectionSize(* (m_reportData->m_pageFooterFirst)); else if ((m_pageCounter % 2) == 1 && m_reportData->m_pageFooterOdd) retval = renderSectionSize(* (m_reportData->m_pageFooterOdd)); else if ((m_pageCounter % 2) == 0 && m_reportData->m_pageFooterEven) retval = renderSectionSize(* (m_reportData->m_pageFooterEven)); else if (m_reportData->m_pageFooterAny) retval = renderSectionSize(* (m_reportData->m_pageFooterAny)); //kDebug() << retval; return retval; } qreal KoReportPreRendererPrivate::finishCurPage(bool lastPage) { qreal offset = m_maxHeight - m_bottomMargin; qreal retval = 0.0; //kDebug() << offset; if (lastPage && m_reportData->m_pageFooterLast) { //kDebug() << "Last Footer"; m_yOffset = offset - renderSectionSize(* (m_reportData->m_pageFooterLast)); retval = renderSection(* (m_reportData->m_pageFooterLast)); } else if (m_pageCounter == 1 && m_reportData->m_pageFooterFirst) { //kDebug() << "First Footer"; m_yOffset = offset - renderSectionSize(* (m_reportData->m_pageFooterFirst)); retval = renderSection(* (m_reportData->m_pageFooterFirst)); } else if ((m_pageCounter % 2) == 1 && m_reportData->m_pageFooterOdd) { //kDebug() << "Odd Footer"; m_yOffset = offset - renderSectionSize(* (m_reportData->m_pageFooterOdd)); retval = renderSection(* (m_reportData->m_pageFooterOdd)); } else if ((m_pageCounter % 2) == 0 && m_reportData->m_pageFooterEven) { //kDebug() << "Even Footer"; m_yOffset = offset - renderSectionSize(* (m_reportData->m_pageFooterEven)); retval = renderSection(* (m_reportData->m_pageFooterEven)); } else if (m_reportData->m_pageFooterAny) { //kDebug() << "Any Footer"; m_yOffset = offset - renderSectionSize(* (m_reportData->m_pageFooterAny)); retval = renderSection(* (m_reportData->m_pageFooterAny)); } return retval; } void KoReportPreRendererPrivate::renderDetailSection(KRDetailSectionData & detailData) { if (detailData.m_detailSection) { if (m_kodata/* && !curs->eof()*/) { QStringList keys; QStringList keyValues; QList shownGroups; ORDetailGroupSectionData * grp = 0; bool status = m_kodata->moveFirst(); m_recordCount = m_kodata->recordCount(); //kDebug() << "Record Count:" << m_recordCount; for (int i = 0; i < (int) detailData.m_groupList.count(); ++i) { grp = detailData.m_groupList[i]; //If the group has a header or footer, then emit a change of group value if(grp->m_groupFooter || grp->m_groupHeader) { // we get here only if group is *shown* shownGroups << i; keys.append(grp->m_column); if (!keys.last().isEmpty()) keyValues.append(m_kodata->value(m_kodata->fieldNumber(keys.last())).toString()); else keyValues.append(QString()); //Tell interested parties we're about to render a header emit(enteredGroup(keys.last(), keyValues.last())); } if (grp->m_groupHeader) renderSection(*(grp->m_groupHeader)); } while (status) { long l = m_kodata->at(); //kDebug() << "At:" << l << "Y:" << m_yOffset << "Max Height:" << m_maxHeight; if (renderSectionSize(*(detailData.m_detailSection)) + finishCurPageSize((l + 1 == m_recordCount)) + m_bottomMargin + m_yOffset >= m_maxHeight) { //kDebug() << "Next section is too big for this page"; if (l > 0) { m_kodata->movePrevious(); createNewPage(); m_kodata->moveNext(); } } renderSection(*(detailData.m_detailSection)); if (m_kodata) status = m_kodata->moveNext(); if (status == true && keys.count() > 0) { // check to see where it is we need to start int pos = -1; // if it's still -1 by the time we are done then no keyValues changed for (int i = 0; i < keys.count(); ++i) { if (keyValues[i] != m_kodata->value(m_kodata->fieldNumber(keys[i])).toString()) { pos = i; break; } } // don't bother if nothing has changed if (pos != -1) { // roll back the query and go ahead if all is good status = m_kodata->movePrevious(); if (status == true) { // print the footers as needed // any changes made in this for loop need to be duplicated // below where the footers are finished. bool do_break = false; for (int i = shownGroups.count() - 1; i >= 0; i--) { if (do_break) createNewPage(); do_break = false; grp = detailData.m_groupList[shownGroups.at(i)]; if (grp->m_groupFooter) { if (renderSectionSize(*(grp->m_groupFooter)) + finishCurPageSize() + m_bottomMargin + m_yOffset >= m_maxHeight) createNewPage(); renderSection(*(grp->m_groupFooter)); } if (ORDetailGroupSectionData::BreakAfterGroupFooter == grp->m_pagebreak) do_break = true; } // step ahead to where we should be and print the needed headers // if all is good status = m_kodata->moveNext(); if (do_break) createNewPage(); if (status == true) { for (int i = 0; i < shownGroups.count(); ++i) { grp = detailData.m_groupList[shownGroups.at(i)]; if (grp->m_groupHeader) { if (renderSectionSize(*(grp->m_groupHeader)) + finishCurPageSize() + m_bottomMargin + m_yOffset >= m_maxHeight) { m_kodata->movePrevious(); createNewPage(); m_kodata->moveNext(); } if (!keys[i].isEmpty()) keyValues[i] = m_kodata->value(m_kodata->fieldNumber(keys[i])).toString(); //Tell interested parties thak key values changed renderSection(*(grp->m_groupHeader)); } } } } } } } if (keys.size() > 0 && m_kodata->movePrevious()) { // finish footers // duplicated changes from above here for (int i = shownGroups.count() - 1; i >= 0; i--) { grp = detailData.m_groupList[shownGroups.at(i)]; if (grp->m_groupFooter) { if (renderSectionSize(*(grp->m_groupFooter)) + finishCurPageSize() + m_bottomMargin + m_yOffset >= m_maxHeight) createNewPage(); renderSection(*(grp->m_groupFooter)); emit(exitedGroup(keys[i], keyValues[i])); } } } } if (KRDetailSectionData::BreakAtEnd == detailData.m_pageBreak) createNewPage(); } } qreal KoReportPreRendererPrivate::renderSectionSize(const KRSectionData & sectionData) { qreal intHeight = POINT_TO_INCH(sectionData.height()) * KoDpi::dpiY(); int itemHeight = 0; if (sectionData.objects().count() == 0) return intHeight; QList objects = sectionData.objects(); foreach(KoReportItemBase *ob, objects) { QPointF offset(m_leftMargin, m_yOffset); QVariant itemData = m_kodata->value(ob->itemDataSource()); //ASync objects cannot alter the section height KoReportASyncItemBase *async_ob = qobject_cast(ob); if (!async_ob) { itemHeight = ob->renderSimpleData(0, 0, offset, itemData, m_scriptHandler); if (itemHeight > intHeight) { intHeight = itemHeight; } } } return intHeight; } qreal KoReportPreRendererPrivate::renderSection(const KRSectionData & sectionData) { qreal sectionHeight = POINT_TO_INCH(sectionData.height()) * KoDpi::dpiY(); int itemHeight = 0; //kDebug() << "Name: " << sectionData.name() << " Height: " << sectionHeight // << "Objects: " << sectionData.objects().count(); emit(renderingSection(const_cast(§ionData), m_page, QPointF(m_leftMargin, m_yOffset))); //Create a pre-rendered section for this section and add it to the document OROSection *sec = new OROSection(m_document); sec->setHeight(sectionData.height()); sec->setBackgroundColor(sectionData.backgroundColor()); sec->setType(sectionData.type()); m_document->addSection(sec); //Render section background ORORect* bg = new ORORect(); bg->setPen(QPen(Qt::NoPen)); bg->setBrush(sectionData.backgroundColor()); qreal w = m_page->document()->pageOptions().widthPx() - m_page->document()->pageOptions().getMarginRight() - m_leftMargin; bg->setRect(QRectF(m_leftMargin, m_yOffset, w, sectionHeight)); m_page->addPrimitive(bg, true); QList objects = sectionData.objects(); foreach(KoReportItemBase *ob, objects) { QPointF offset(m_leftMargin, m_yOffset); QVariant itemData = m_kodata->value(ob->itemDataSource()); if (ob->supportsSubQuery()) { itemHeight = ob->renderReportData(m_page, sec, offset, m_kodata, m_scriptHandler); } else { KoReportASyncItemBase *async_ob = qobject_cast(ob); if (async_ob){ //kDebug() << "async object"; asyncManager->addItem(async_ob, m_page, sec, offset, async_ob->realItemData(itemData), m_scriptHandler); } else { //kDebug() << "sync object"; itemHeight = ob->renderSimpleData(m_page, sec, offset, itemData, m_scriptHandler); } } if (itemHeight > sectionHeight) { sectionHeight = itemHeight; } } for (int i = 0; i < m_page->primitives(); ++i) { OROPrimitive *prim = m_page->primitive(i); if (prim->type() == OROTextBox::TextBox) { OROTextBox *text = static_cast(prim); if (text->requiresPostProcessing()) { m_postProcText.append(text); } } } m_yOffset += sectionHeight; return sectionHeight; } void KoReportPreRendererPrivate::initEngine() { m_scriptHandler = new KRScriptHandler(m_kodata, m_reportData); connect(this, SIGNAL(enteredGroup(QString,QVariant)), m_scriptHandler, SLOT(slotEnteredGroup(QString,QVariant))); connect(this, SIGNAL(exitedGroup(QString,QVariant)), m_scriptHandler, SLOT(slotExitedGroup(QString,QVariant))); connect(this, SIGNAL(renderingSection(KRSectionData*,OROPage*,QPointF)), m_scriptHandler, SLOT(slotEnteredSection(KRSectionData*,OROPage*,QPointF))); } void KoReportPreRendererPrivate::asyncItemsFinished() { //kDebug() << "Finished rendering async items"; delete asyncManager; } - -//===========================KoReportPreRenderer=============================== - -KoReportPreRenderer::KoReportPreRenderer(const QDomElement & pDocument) : d(new KoReportPreRendererPrivate()) -{ - setDom(pDocument); -} - -KoReportPreRenderer::~KoReportPreRenderer() -{ - delete d; -} - -void KoReportPreRenderer::setName(const QString &n) +bool KoReportPreRendererPrivate::generateDocument() { - d->m_reportData->setName(n); -} - -ORODocument* KoReportPreRenderer::generate() -{ - //kDebug(); - if (d == 0 || !d->m_valid || d->m_reportData == 0 || d->m_kodata == 0) - return 0; - + if (!m_valid || !m_reportData || !m_kodata) { + return false; + } // Do this check now so we don't have to undo a lot of work later if it fails LabelSizeInfo label; - if (d->m_reportData->page.getPageSize() == "Labels") { - label = LabelSizeInfo::find(d->m_reportData->page.getLabelType()); - if (label.isNull()) - return 0; + if (m_reportData->page.getPageSize() == "Labels") { + label = LabelSizeInfo::find(m_reportData->page.getLabelType()); + if (label.isNull()) { + return false; + } } - //kDebug() << "Creating Document"; - d->m_document = new ORODocument(d->m_reportData->m_title); - - d->m_pageCounter = 0; - d->m_yOffset = 0.0; + m_document = new ORODocument(m_reportData->m_title); + m_pageCounter = 0; + m_yOffset = 0.0; //kDebug() << "Calculating Margins"; if (!label.isNull()) { - if (d->m_reportData->page.isPortrait()) { - d->m_topMargin = (label.startY() / 100.0); - d->m_bottomMargin = 0; - d->m_rightMargin = 0; - d->m_leftMargin = (label.startX() / 100.0); + if (m_reportData->page.isPortrait()) { + m_topMargin = (label.startY() / 100.0); + m_bottomMargin = 0; + m_rightMargin = 0; + m_leftMargin = (label.startX() / 100.0); } else { - d->m_topMargin = (label.startX() / 100.0); - d->m_bottomMargin = 0; - d->m_rightMargin = 0; - d->m_leftMargin = (label.startY() / 100.0); + m_topMargin = (label.startX() / 100.0); + m_bottomMargin = 0; + m_rightMargin = 0; + m_leftMargin = (label.startY() / 100.0); } } else { - d->m_topMargin = d->m_reportData->page.getMarginTop(); - d->m_bottomMargin = d->m_reportData->page.getMarginBottom(); - d->m_rightMargin = d->m_reportData->page.getMarginRight(); - d->m_leftMargin = d->m_reportData->page.getMarginLeft(); - //kDebug() << "Margins:" << d->m_topMargin << d->m_bottomMargin << d->m_rightMargin << d->m_leftMargin; + m_topMargin = m_reportData->page.getMarginTop(); + m_bottomMargin = m_reportData->page.getMarginBottom(); + m_rightMargin = m_reportData->page.getMarginRight(); + m_leftMargin = m_reportData->page.getMarginLeft(); + //kDebug() << "Margins:" << m_topMargin << m_bottomMargin << m_rightMargin << m_leftMargin; } //kDebug() << "Calculating Page Size"; - ReportPageOptions rpo(d->m_reportData->page); + ReportPageOptions rpo(m_reportData->page); // This should reflect the information of the report page size - if (d->m_reportData->page.getPageSize() == "Custom") { - d->m_maxWidth = d->m_reportData->page.getCustomWidth(); - d->m_maxHeight = d->m_reportData->page.getCustomHeight(); + if (m_reportData->page.getPageSize() == "Custom") { + m_maxWidth = m_reportData->page.getCustomWidth(); + m_maxHeight = m_reportData->page.getCustomHeight(); } else { if (!label.isNull()) { - d->m_maxWidth = label.width(); - d->m_maxHeight = label.height(); + m_maxWidth = label.width(); + m_maxHeight = label.height(); rpo.setPageSize(label.paper()); } else { // lookup the correct size information for the specified size paper - d->m_maxWidth = KoPageFormat::width(KoPageFormat::formatFromString(d->m_reportData->page.getPageSize()), KoPageFormat::Portrait); - d->m_maxHeight = KoPageFormat::height(KoPageFormat::formatFromString(d->m_reportData->page.getPageSize()), KoPageFormat::Portrait); + m_maxWidth = KoPageFormat::width(KoPageFormat::formatFromString(m_reportData->page.getPageSize()), KoPageFormat::Portrait); + m_maxHeight = KoPageFormat::height(KoPageFormat::formatFromString(m_reportData->page.getPageSize()), KoPageFormat::Portrait); KoUnit pageUnit(KoUnit::Millimeter); - d->m_maxWidth = KoUnit::toInch(pageUnit.fromUserValue(d->m_maxWidth)) * KoDpi::dpiX(); - d->m_maxHeight = KoUnit::toInch(pageUnit.fromUserValue(d->m_maxHeight)) * KoDpi::dpiY(); + m_maxWidth = KoUnit::toInch(pageUnit.fromUserValue(m_maxWidth)) * KoDpi::dpiX(); + m_maxHeight = KoUnit::toInch(pageUnit.fromUserValue(m_maxHeight)) * KoDpi::dpiY(); } } - if (!d->m_reportData->page.isPortrait()) { - qreal tmp = d->m_maxWidth; - d->m_maxWidth = d->m_maxHeight; - d->m_maxHeight = tmp; + if (!m_reportData->page.isPortrait()) { + qreal tmp = m_maxWidth; + m_maxWidth = m_maxHeight; + m_maxHeight = tmp; } - //kDebug() << "Page Size:" << d->m_maxWidth << d->m_maxHeight; + //kDebug() << "Page Size:" << m_maxWidth << m_maxHeight; - d->m_document->setPageOptions(rpo); - d->m_kodata->setSorting(d->m_reportData->m_detailSection->m_sortedFields); - d->m_kodata->open(); - d->initEngine(); + m_document->setPageOptions(rpo); + m_kodata->setSorting(m_reportData->m_detailSection->m_sortedFields); + if (!m_kodata->open()) { + return false; + } + initEngine(); //Loop through all abjects that have been registered, and register them with the script handler - if (d->m_scriptHandler) { - QMapIterator i(m_scriptObjects); + if (m_scriptHandler) { + QMapIterator i(scriptObjects); while (i.hasNext()) { i.next(); - d->m_scriptHandler->registerScriptObject(i.value(), i.key()); + m_scriptHandler->registerScriptObject(i.value(), i.key()); //!TODO This is a hack - if (i.key() == "field") - QObject::connect(d->m_scriptHandler, SIGNAL(groupChanged(QString)), i.value(), SLOT(setWhere(QString))); + if (i.key() == "field") { + QObject::connect(m_scriptHandler, SIGNAL(groupChanged(QString)), i.value(), SLOT(setWhere(QString))); + } } } //execute the script - d->m_scriptHandler->trigger(); + m_scriptHandler->trigger(); - d->createNewPage(); + createNewPage(); if (!label.isNull()) { // Label Print Run // remember the initial margin setting as we will be modifying // the value and restoring it as we move around - qreal margin = d->m_leftMargin; + qreal margin = m_leftMargin; - d->m_yOffset = d->m_topMargin; + m_yOffset = m_topMargin; qreal w = (label.width() / 100.0); qreal wg = (label.xGap() / 100.0); qreal h = (label.height() / 100.0); qreal hg = (label.yGap() / 100.0); int numCols = label.columns(); int numRows = label.rows(); qreal tmp; // flip the value around if we are printing landscape - if (!d->m_reportData->page.isPortrait()) { + if (!m_reportData->page.isPortrait()) { w = (label.height() / 100.0); wg = (label.yGap() / 100.0); h = (label.width() / 100.0); hg = (label.xGap() / 100.0); numCols = label.rows(); numRows = label.columns(); } - KRDetailSectionData * detailData = d->m_reportData->m_detailSection; + KRDetailSectionData * detailData = m_reportData->m_detailSection; if (detailData->m_detailSection) { - KoReportData *mydata = d->m_kodata; + KoReportData *mydata = m_kodata; if (mydata && mydata->recordCount() > 0) { /* && !((query = orqThis->getQuery())->eof()))*/ - mydata->moveFirst(); + if (!mydata->moveFirst()) { + return false; + } int row = 0; int col = 0; do { - tmp = d->m_yOffset; // store the value as renderSection changes it - d->renderSection(*(detailData->m_detailSection)); - d->m_yOffset = tmp; // restore the value that renderSection modified + tmp = m_yOffset; // store the value as renderSection changes it + renderSection(*detailData->m_detailSection); + m_yOffset = tmp; // restore the value that renderSection modified col++; - d->m_leftMargin += w + wg; + m_leftMargin += w + wg; if (col >= numCols) { - d->m_leftMargin = margin; // reset back to original value + m_leftMargin = margin; // reset back to original value col = 0; row++; - d->m_yOffset += h + hg; + m_yOffset += h + hg; if (row >= numRows) { - d->m_yOffset = d->m_topMargin; + m_yOffset = m_topMargin; row = 0; - d->createNewPage(); + createNewPage(); } } } while (mydata->moveNext()); } } } else { // Normal Print Run - if (d->m_reportData->m_reportHeader) { - d->renderSection(*(d->m_reportData->m_reportHeader)); + if (m_reportData->m_reportHeader) { + renderSection(*m_reportData->m_reportHeader); } - if (d->m_reportData->m_detailSection) { - d->renderDetailSection(*(d->m_reportData->m_detailSection)); + if (m_reportData->m_detailSection) { + renderDetailSection(*m_reportData->m_detailSection); } - if (d->m_reportData->m_reportFooter) { - if (d->renderSectionSize(*(d->m_reportData->m_reportFooter)) + d->finishCurPageSize(true) + d->m_bottomMargin + d->m_yOffset >= d->m_maxHeight) { - d->createNewPage(); + if (m_reportData->m_reportFooter) { + if (renderSectionSize(*m_reportData->m_reportFooter) + finishCurPageSize(true) + m_bottomMargin + m_yOffset >= m_maxHeight) { + createNewPage(); } - d->renderSection(*(d->m_reportData->m_reportFooter)); + renderSection(*m_reportData->m_reportFooter); } } - d->finishCurPage(true); + finishCurPage(true); // _postProcText contains those text boxes that need to be updated // with information that wasn't available at the time it was added to the document - d->m_scriptHandler->setPageTotal(d->m_document->pages()); + m_scriptHandler->setPageTotal(m_document->pages()); - for (int i = 0; i < d->m_postProcText.size(); i++) { - OROTextBox * tb = d->m_postProcText.at(i); + for (int i = 0; i < m_postProcText.size(); i++) { + OROTextBox * tb = m_postProcText.at(i); - d->m_scriptHandler->setPageNumber(tb->page()->page() + 1); + m_scriptHandler->setPageNumber(tb->page()->page() + 1); - tb->setText(d->m_scriptHandler->evaluate(tb->text()).toString()); + tb->setText(m_scriptHandler->evaluate(tb->text()).toString()); } - d->asyncManager->startRendering(); + asyncManager->startRendering(); + + m_scriptHandler->displayErrors(); - d->m_scriptHandler->displayErrors(); + if (!m_kodata->close()) { + return false; + } + delete m_scriptHandler; + delete m_kodata; + m_postProcText.clear(); + return true; +} + +//===========================KoReportPreRenderer=============================== - d->m_kodata->close(); - delete d->m_scriptHandler; - delete d->m_kodata; - d->m_postProcText.clear(); +KoReportPreRenderer::KoReportPreRenderer(const QDomElement & pDocument) : d(new KoReportPreRendererPrivate()) +{ + setDom(pDocument); +} +KoReportPreRenderer::~KoReportPreRenderer() +{ + delete d; +} + +void KoReportPreRenderer::setName(const QString &n) +{ + d->m_reportData->setName(n); +} + +ORODocument* KoReportPreRenderer::document() +{ + return d->m_document; +} + +bool KoReportPreRenderer::generateDocument() +{ + delete d->m_document; + if (!d->generateDocument()) { + delete d->m_document; + d->m_document = 0; + } return d->m_document; } void KoReportPreRenderer::setSourceData(KoReportData *data) { if (d && data) { d->m_kodata = data; } } bool KoReportPreRenderer::setDom(const QDomElement &docReport) { if (d) { delete d->m_reportData; d->m_valid = false; if (docReport.tagName() != "report:content") { kWarning() << "report schema is invalid"; return false; } d->m_reportData = new KoReportReportData(docReport, this); d->m_valid = d->m_reportData->isValid(); } return isValid(); } bool KoReportPreRenderer::isValid() const { if (d && d->m_valid) return true; return false; } void KoReportPreRenderer::registerScriptObject(QObject* obj, const QString& name) { //kDebug() << name; - m_scriptObjects[name] = obj; + d->scriptObjects[name] = obj; } const KoReportReportData* KoReportPreRenderer::reportData() const { return d->m_reportData; } #include "KoReportPreRenderer.moc" diff --git a/libs/koreport/renderer/KoReportPreRenderer.h b/libs/koreport/renderer/KoReportPreRenderer.h index 321988b9732..2369532d888 100644 --- a/libs/koreport/renderer/KoReportPreRenderer.h +++ b/libs/koreport/renderer/KoReportPreRenderer.h @@ -1,69 +1,70 @@ /* * OpenRPT report writer and rendering engine * Copyright (C) 2001-2007 by OpenMFG, LLC (info@openmfg.com) * Copyright (C) 2007-2008 by Adam Pigg (adam@piggz.co.uk) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #ifndef __KOREPORTPRERENDERER_H__ #define __KOREPORTPRERENDERER_H__ #include #include #include #include #include #include "koreport_export.h" #include "scripting/krscripthandler.h" class KoReportPreRendererPrivate; class ParameterList; class ORODocument; class KoReportData; // // ORPreRender // This class takes a report definition and prerenders the result to // an ORODocument that can be used to pass to any number of renderers. // class KOREPORT_EXPORT KoReportPreRenderer : public QObject { public: explicit KoReportPreRenderer(const QDomElement&); virtual ~KoReportPreRenderer(); void setSourceData(KoReportData*); void registerScriptObject(QObject*, const QString&); - - ORODocument * generate(); + + bool generateDocument(); + + ORODocument *document(); /** @brief Set the name of the report so that it can be used internally by the script engine */ void setName(const QString &); bool isValid() const; const KoReportReportData *reportData() const; protected: private: KoReportPreRendererPrivate *const d; bool setDom(const QDomElement &); - QMap m_scriptObjects; }; #endif // __KOREPORTPRERENDERER_H__ diff --git a/libs/koreport/tests/KoReportTest.cpp b/libs/koreport/tests/KoReportTest.cpp index f3a8d281d9a..546e886c2ee 100644 --- a/libs/koreport/tests/KoReportTest.cpp +++ b/libs/koreport/tests/KoReportTest.cpp @@ -1,188 +1,188 @@ /* * OpenRPT report writer and rendering engine * Copyright (C) 2012 Dag Andersen * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "KoReportTest.h" #include #include "KoReportPreRenderer.h" #include "KoReportDesigner.h" #include "krreportdata.h" #include "reportpageoptions.h" #include "reportsectiondetail.h" #include "reportsection.h" #include "krpos.h" #include "krsize.h" #include "KoReportDesignerItemLine.h" #include "KoReportItemLine.h" #include "KoReportDesignerItemRectBase.h" #include #include #include "KoUnit.h" #include "KoDpi.h" #include #include void KoReportTest::pageOptions() { QString s; s += ""; s += "Report"; s += ""; s += "predefined"; // needs detail section, or else designer crash s += ""; s += ""; s += ""; s += ""; s += ""; QDomDocument doc; doc.setContent( s ); KoReportDesigner designer(0, doc.documentElement()); QCOMPARE(designer.propertySet()->property("page-size").value().toString(), QString("A5")); QCOMPARE(designer.propertySet()->property("margin-bottom").value().toDouble(), KoUnit::parseValue("1.5cm")); QCOMPARE(designer.propertySet()->property("margin-top").value().toDouble(), KoUnit::parseValue("2.0cm")); QCOMPARE(designer.propertySet()->property("margin-left").value().toDouble(), KoUnit::parseValue("3.0cm")); QCOMPARE(designer.propertySet()->property("margin-right").value().toDouble(), KoUnit::parseValue("4.0cm")); KoReportPreRenderer renderer( designer.document() ); - renderer.generate(); + QVERIFY(renderer.generateDocument()); ReportPageOptions opt = renderer.reportData()->pageOptions(); QCOMPARE(opt.getPageSize(), QString("A5")); QCOMPARE(QString::number(INCH_TO_POINT(opt.getMarginBottom()) / KoDpi::dpiY()), QString::number(KoUnit::parseValue("1.5cm"))); QCOMPARE(QString::number(INCH_TO_POINT(opt.getMarginTop()) / KoDpi::dpiY()), QString::number(KoUnit::parseValue("2.0cm"))); QCOMPARE(QString::number(INCH_TO_POINT(opt.getMarginLeft()) / KoDpi::dpiY()), QString::number(KoUnit::parseValue("3.0cm"))); QCOMPARE(QString::number(INCH_TO_POINT(opt.getMarginRight()) / KoDpi::dpiY()), QString::number(KoUnit::parseValue("4.0cm"))); } void KoReportTest::lineItem() { QString s; s += ""; s += "Report"; s += ""; s += "predefined"; s += ""; s += ""; s += ""; s += ""; s += ""; s += ""; s += ""; s += ""; s += ""; QDomDocument doc; doc.setContent( s ); KoReportDesigner designer(0, doc.documentElement()); ReportSectionDetail *ds = designer.detailSection(); ReportSection *sec = ds->detailSection(); KoReportItemLine *l = dynamic_cast(sec->items().first()); QVERIFY(l != 0); QCOMPARE(l->Z, 1.5); KRPos start = l->startPosition(); KRPos end = l->endPosition(); QCOMPARE(start.toPoint(), QPointF(KoUnit::parseValue("1.5cm"), KoUnit::parseValue("0.5cm"))); QCOMPARE(end.toPoint(), QPointF(KoUnit::parseValue("4.5cm"), KoUnit::parseValue("2.5cm"))); KoReportPreRenderer renderer( designer.document() ); - renderer.generate(); + QVERIFY(renderer.generateDocument()); l = dynamic_cast(renderer.reportData()->object("line1")); QVERIFY(l != 0); QCOMPARE(l->Z, 1.5); start = l->startPosition(); end = l->endPosition(); QCOMPARE(start.toPoint(), QPointF(KoUnit::parseValue("1.5cm"), KoUnit::parseValue("0.5cm"))); QCOMPARE(end.toPoint(), QPointF(KoUnit::parseValue("4.5cm"), KoUnit::parseValue("2.5cm"))); } void KoReportTest::rectItem() { // Use a label to test basic rect properties QString s; s += ""; s += "Report"; s += ""; s += "predefined"; s += ""; s += ""; s += ""; s += ""; s += " "; s += " "; s += ""; s += ""; s += ""; s += ""; QDomDocument doc; doc.setContent( s ); KoReportDesigner designer(0, doc.documentElement()); ReportSectionDetail *ds = designer.detailSection(); ReportSection *sec = ds->detailSection(); QVERIFY(sec->items().count() == 1); KoReportDesignerItemRectBase *rect = dynamic_cast(sec->items().first()); QVERIFY(rect != 0); QRectF expected( QPointF(KoUnit::parseValue("1.5cm"), KoUnit::parseValue("0.5cm")), QSizeF(KoUnit::parseValue("4.5cm"), KoUnit::parseValue("0.75cm"))); QCOMPARE(rect->pointRect(), expected); KoReportPreRenderer renderer( designer.document() ); - renderer.generate(); + QVERIFY(renderer.generateDocument()); KoReportItemBase *item = dynamic_cast(renderer.reportData()->object("label1")); QVERIFY(item != 0); KRPos pos = item->position(); KRSize size = item->size(); QCOMPARE(pos.toPoint().x(), KoUnit::parseValue("1.5cm")); QCOMPARE(pos.toPoint().y(), KoUnit::parseValue("0.5cm")); QCOMPARE(size.toPoint(), QSizeF(KoUnit::parseValue("4.5cm"), KoUnit::parseValue("0.75cm"))); QCOMPARE(size.toPoint(), QSizeF(KoUnit::parseValue("4.5cm"), KoUnit::parseValue("0.75cm"))); } QTEST_KDEMAIN(KoReportTest, GUI) #include "moc_KoReportTest.cpp" diff --git a/plan/libs/ui/reports/reportview.cpp b/plan/libs/ui/reports/reportview.cpp index be9a423d298..462a31d0537 100644 --- a/plan/libs/ui/reports/reportview.cpp +++ b/plan/libs/ui/reports/reportview.cpp @@ -1,1705 +1,1705 @@ /* * KPlato Report Plugin * Copyright (C) 2007-2008 by Adam Pigg (adam@piggz.co.uk) * Copyright (C) 2010, 2011, 2012 by Dag Andersen * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser 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 "reportview.h" #include "reportview_p.h" #include "report.h" #include "reportdata.h" #include "reportsourceeditor.h" #include "reportscripts.h" #include "reportexportpanel.h" #include "ui_reportsectionswidget.h" #include "ui_reporttoolswidget.h" #include "KoReportODTRenderer.h" #include "KoReportPage.h" #include "KoReportPreRenderer.h" #include "KoReportPrintRenderer.h" #include "renderobjects.h" #include "KoReportKSpreadRenderer.h" #include "KoReportHTMLCSSRenderer.h" #include "reportsection.h" #include "reportsectiondetail.h" #include "reportsectiondetailgroup.h" #include "koproperty/EditorView.h" #include "kptglobal.h" #include "kptaccountsmodel.h" #include "kptflatproxymodel.h" #include "kptnodeitemmodel.h" #include "kpttaskstatusmodel.h" #include "kptresourcemodel.h" #include "kptresourceappointmentsmodel.h" #include "kptschedule.h" #include "kptnodechartmodel.h" #include "kptdebug.h" #include "KoPageLayout.h" #include "KoDocument.h" #include "KoIcon.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace KPlato { //---------------- ReportPrintingDialog::ReportPrintingDialog( ViewBase *view, ORODocument *reportDocument ) : KoPrintingDialog( view ), m_reportDocument( reportDocument ) { printer().setFromTo( documentFirstPage(), documentLastPage() ); m_context.printer = &printer(); m_context.painter = 0; KoReportRendererFactory factory; m_renderer = factory.createInstance( "print" ); //FIXME: This should be done by KoReportPrintRender but setupPrinter() is private QPrinter *pPrinter = &printer(); pPrinter->setCreator("Plan"); pPrinter->setDocName(reportDocument->title()); pPrinter->setFullPage(true); pPrinter->setOrientation((reportDocument->pageOptions().isPortrait() ? QPrinter::Portrait : QPrinter::Landscape)); pPrinter->setPageOrder(QPrinter::FirstPageFirst); if (reportDocument->pageOptions().getPageSize().isEmpty()) pPrinter->setPageSize(QPrinter::Custom); else pPrinter->setPageSize(KoPageFormat::printerPageSize(KoPageFormat::formatFromString(reportDocument->pageOptions().getPageSize()))); //FIXME: There is something wrong with koreport margins qreal left = reportDocument->pageOptions().getMarginLeft(); qreal top = reportDocument->pageOptions().getMarginTop(); qreal right = reportDocument->pageOptions().getMarginRight(); qreal bottom = reportDocument->pageOptions().getMarginBottom(); pPrinter->setPageMargins( left, top, right, bottom, QPrinter::Point ); } ReportPrintingDialog::~ReportPrintingDialog() { delete m_renderer; } void ReportPrintingDialog::startPrinting( RemovePolicy removePolicy ) { kDebug(planDbg()); QPainter p( &printer() ); printPage( 1, p ); if ( removePolicy == DeleteWhenDone ) { deleteLater(); } } int ReportPrintingDialog::documentLastPage() const { return m_reportDocument->pages(); } void ReportPrintingDialog::printPage( int page, QPainter &painter ) { m_context.painter = &painter; m_renderer->render( m_context, m_reportDocument, page ); } QAbstractPrintDialog::PrintDialogOptions ReportPrintingDialog::printDialogOptions() const { return QAbstractPrintDialog::PrintToFile | QAbstractPrintDialog::PrintPageRange | QAbstractPrintDialog::PrintCollateCopies | QAbstractPrintDialog::DontUseSheet; } //--------------------- ReportView::ReportView(KoPart *part, KoDocument *doc, QWidget *parent ) : ViewBase(part, doc, parent ) { // kDebug(planDbg())<<"--------------- ReportView ------------------"; setObjectName("ReportView"); QLayout *l = new QHBoxLayout( this ); l->setMargin(0); m_stack = new QStackedWidget( this ); l->addWidget( m_stack ); ReportWidget *v = new ReportWidget(part, doc, m_stack); m_stack->addWidget( v ); connect(v, SIGNAL(editReportDesign()),SLOT(slotEditReport())); connect(v, SIGNAL(guiActivated(ViewBase*,bool)), SIGNAL(guiActivated(ViewBase*,bool))); ReportDesigner *d = new ReportDesigner(part, doc, m_stack); m_stack->addWidget( d ); connect(d, SIGNAL(viewReport()), SLOT(slotViewReport())); connect(d, SIGNAL(guiActivated(ViewBase*,bool)), SIGNAL(guiActivated(ViewBase*,bool))); connect(d, SIGNAL(optionsModified()), SIGNAL(optionsModified())); m_stack->setCurrentIndex( 0 ); } void ReportView::slotEditReport() { reportWidget()->setGuiActive( false ); m_stack->setCurrentIndex( 1 ); reportDesigner()->setGuiActive( true ); } void ReportView::slotViewReport() { reportDesigner()->setGuiActive( false ); if ( reportWidget()->documentIsNull() || reportDesigner()->isModified() ) { reportWidget()->loadXML( reportDesigner()->document() ); } if ( reportDesigner()->isModified() ) { emit optionsModified(); reportDesigner()->setModified( false ); } m_stack->setCurrentIndex( 0 ); reportWidget()->setGuiActive( true ); } void ReportView::setProject( Project *project ) { reportWidget()->setProject( project ); reportDesigner()->setProject( project ); } void ReportView::setScheduleManager( ScheduleManager *sm ) { reportWidget()->setScheduleManager( sm ); reportDesigner()->setScheduleManager( sm ); } KoPrintJob *ReportView::createPrintJob() { return static_cast( m_stack->currentWidget() )->createPrintJob(); } void ReportView::setGuiActive( bool active ) { return static_cast( m_stack->currentWidget() )->setGuiActive( active ); } bool ReportView::loadXML( const QDomDocument &doc ) { reportDesigner()->setData( doc ); return reportWidget()->loadXML( doc ); } bool ReportView::loadContext( const KoXmlElement &context ) { bool res = true; // designer first, widget uses it's data res = reportDesigner()->loadContext( context ); res &= reportWidget()->loadContext( context ); reportWidget()->loadXML( reportDesigner()->document() ); return res; } void ReportView::saveContext( QDomElement &context ) const { QDomElement e = context.ownerDocument().createElement( "view" ); context.appendChild( e ); e.setAttribute( "current-view", m_stack->currentIndex() ); reportDesigner()->saveContext( context ); reportWidget()->saveContext( context ); } ReportWidget *ReportView::reportWidget() const { return static_cast( m_stack->widget( 0 ) ); } ReportDesigner *ReportView::reportDesigner() const { return static_cast( m_stack->widget( 1 ) ); } QDomDocument ReportView::document() const { return reportDesigner()->document(); } QList< ReportData* > ReportView::reportDataModels() const { return reportWidget()->reportDataModels(); } //--------------------- ReportWidget::ReportWidget(KoPart *part, KoDocument *doc, QWidget *parent ) : ViewBase(part, doc, parent ), m_reportdatamodels( Report::createBaseReportDataModels() ) { // kDebug(planDbg())<<"--------------- ReportWidget ------------------"; m_preRenderer = 0; setObjectName("ReportWidget"); m_reportView = new QGraphicsView(this); m_reportScene = new QGraphicsScene(this); m_reportScene->setSceneRect(0,0,1000,2000); m_reportView->setScene(m_reportScene); m_reportScene->setBackgroundBrush(palette().brush(QPalette::Dark)); QVBoxLayout *l = new QVBoxLayout( this ); l->setMargin(0); l->addWidget( m_reportView ); m_pageSelector = new ReportNavigator( this ); l->addWidget( m_pageSelector ); setupGui(); connect(m_pageSelector->ui_next, SIGNAL(clicked()), this, SLOT(nextPage())); connect(m_pageSelector->ui_prev, SIGNAL(clicked()), this, SLOT(prevPage())); connect(m_pageSelector->ui_first, SIGNAL(clicked()), this, SLOT(firstPage())); connect(m_pageSelector->ui_last, SIGNAL(clicked()), this, SLOT(lastPage())); connect(m_pageSelector->ui_selector, SIGNAL(valueChanged(int)), SLOT(renderPage(int))); slotRefreshView(); } //----------------- void ReportWidget::renderPage( int page ) { m_reportPage->renderPage( page ); } void ReportWidget::nextPage() { m_pageSelector->ui_selector->setValue( m_pageSelector->ui_selector->value() + 1 ); } void ReportWidget::prevPage() { m_pageSelector->ui_selector->setValue( m_pageSelector->ui_selector->value() - 1 ); } void ReportWidget::firstPage() { m_pageSelector->ui_selector->setValue( 1 ); } void ReportWidget::lastPage() { m_pageSelector->ui_selector->setValue( m_pageSelector->ui_max->value() ); } KoPrintJob *ReportWidget::createPrintJob() { - return new ReportPrintingDialog( this, m_reportDocument ); + return new ReportPrintingDialog(this, m_preRenderer->document()); } void ReportWidget::slotExport() { ReportExportPanel *p = new ReportExportPanel(); p->setObjectName( "ReportExportPanel" ); KFileDialog *dia = new KFileDialog( KUrl(), QString(), this, p ); dia->setOperationMode( KFileDialog::Saving ); dia->setMode( KFile::File ); dia->setConfirmOverwrite( true ); dia->setInlinePreviewShown( true ); dia->setCaption( i18nc( "@title:window", "Export Report" ) ); // dia->setFilter( QString( "*.ods|%1\n*|%2" ).arg( i18n( "Open document spreadsheet" ) ).arg( i18n( "All Files" ) ) ); connect(dia, SIGNAL(finished(int)), SLOT(slotExportFinished(int))); dia->show(); dia->raise(); dia->activateWindow(); } KoPageLayout ReportWidget::pageLayout() const { KoPageLayout p = ViewBase::pageLayout(); - ReportPageOptions opt = m_reportDocument->pageOptions(); + ReportPageOptions opt = m_preRenderer->document()->pageOptions(); p.orientation = opt.isPortrait() ? KoPageFormat::Portrait : KoPageFormat::Landscape; if (opt.getPageSize().isEmpty()) { p.format = KoPageFormat::CustomSize; p.width = opt.getCustomWidth(); p.height = opt.getCustomHeight(); } else { p.format = KoPageFormat::formatFromString(opt.getPageSize()); } p.topMargin = opt.getMarginTop(); p.bottomMargin = opt.getMarginBottom(); p.leftMargin = opt.getMarginLeft(); p.rightMargin = opt.getMarginRight(); p.pageEdge = 0.0; p.bindingSide = 0.0; return p; } void ReportWidget::slotExportFinished( int result ) { //TODO confirm overwrite KFileDialog *dia = dynamic_cast( sender() ); if ( dia == 0 ) { return; } ReportExportPanel *p = dia->findChild("ReportExportPanel"); Q_ASSERT( p ); if ( p && result == QDialog::Accepted ) { KoReportRendererContext context; context.destinationUrl = dia->selectedUrl(); if (! context.destinationUrl.isValid() ) { KMessageBox::error(this, i18nc( "@info", "Cannot export report. Invalid url:
file:
%1", context.destinationUrl.url() ), i18n( "Not Saved" ) ); } else { switch ( p->selectedFormat() ) { case Reports::EF_OdtTable: exportToOdtTable( context ); break; case Reports::EF_OdtFrames: exportToOdtFrames( context ); break; case Reports::EF_Ods: exportToOds( context ); break; case Reports::EF_Html: exportToHtml( context ); break; case Reports::EF_XHtml: exportToXHtml( context ); break; default: KMessageBox::error(this, i18n("Cannot export report. Unknown file format"), i18n( "Not Saved" ) ); break; } } } dia->deleteLater(); } void ReportWidget::exportToOdtTable( KoReportRendererContext &context ) { kDebug(planDbg())<<"Export to odt:"<render(context, m_reportDocument)) { + if (!renderer->render(context, m_preRenderer->document())) { KMessageBox::error(this, i18nc( "@info", "Failed to export to %1", context.destinationUrl.prettyUrl()) , i18n("Export to text document failed")); } } void ReportWidget::exportToOdtFrames( KoReportRendererContext &context ) { kDebug(planDbg())<<"Export to odt:"<render(context, m_reportDocument)) { + if (!renderer->render(context, m_preRenderer->document())) { KMessageBox::error(this, i18nc( "@info", "Failed to export to %1", context.destinationUrl.prettyUrl()) , i18n("Export to text document failed")); } } void ReportWidget::exportToOds( KoReportRendererContext &context ) { kDebug(planDbg())<<"Export to ods:"<render(context, m_reportDocument)) { + if (!renderer->render(context, m_preRenderer->document())) { KMessageBox::error(this, i18nc( "@info", "Failed to export to %1", context.destinationUrl.prettyUrl()) , i18n("Export to spreadsheet failed")); } } void ReportWidget::exportToHtml( KoReportRendererContext &context ) { kDebug(planDbg())<<"Export to html:"<render(context, m_reportDocument)) { + if (!renderer->render(context, m_preRenderer->document())) { KMessageBox::error(this, i18nc( "@info", "Failed to export to %1", context.destinationUrl.prettyUrl()) , i18n("Export to HTML failed")); } } void ReportWidget::exportToXHtml( KoReportRendererContext &context ) { kDebug(planDbg())<<"Export to xhtml:"<render(context, m_reportDocument)) { + if (!renderer->render(context, m_preRenderer->document())) { KMessageBox::error(this, i18nc( "@info", "Failed to export to %1", context.destinationUrl.prettyUrl()) , i18n("Export to XHTML failed")); } } void ReportWidget::setupGui() { /*KActionCollection *coll = actionCollection();*/ KAction *a = 0; QString name = "reportview_list"; a = new KAction(koIcon("go-next-view"), i18n("Edit Report"), this); a->setToolTip( i18nc( "@info:tooltip", "Edit the report definition" ) ); a->setWhatsThis( i18nc( "@info:whatsthis", "Opens the report design in the report design dialog." ) ); connect(a, SIGNAL(triggered()), this, SIGNAL(editReportDesign())); addAction( name, a ); a = new KAction(koIcon("document-export"), i18n("Export"), this); a->setToolTip( i18nc( "@info:tooltip", "Export to file" ) ); a->setWhatsThis( i18nc( "@info:whatsthis", "Exports the report to a supported file format." ) ); connect(a, SIGNAL(triggered()), this, SLOT(slotExport())); addAction( name, a ); } void ReportWidget::setGuiActive( bool active ) // virtual slot { if ( active ) { slotRefreshView(); } ViewBase::setGuiActive( active ); } void ReportWidget::slotRefreshView() { if ( ! isVisible() ) { kDebug(planDbg())<<"Not visible"; return; } delete m_preRenderer; QDomElement e = m_design.documentElement(); m_preRenderer = new KoReportPreRenderer( e.firstChildElement( "report:content" ) ); if ( ! m_preRenderer->isValid()) { kDebug(planDbg())<<"Invalid design document"; return; } ReportData *rd = createReportData( e ); m_preRenderer->setSourceData( rd ); m_preRenderer->registerScriptObject(new ProjectAccess( rd ), "project"); - m_reportDocument = m_preRenderer->generate(); - m_pageSelector->setMaximum( m_reportDocument ? m_reportDocument->pages() : 1 ); + const bool generated = m_preRenderer->generateDocument(); + m_pageSelector->setMaximum(generated ? m_preRenderer->document()->pages() : 1); m_pageSelector->setCurrentPage( 1 ); - m_reportPage = new KoReportPage(this, m_reportDocument); + m_reportPage = new KoReportPage(this, m_preRenderer->document()); m_reportPage->setObjectName("ReportPage"); m_reportScene->setSceneRect(0,0,m_reportPage->rect().width() + 40, m_reportPage->rect().height() + 40); m_reportScene->addItem(m_reportPage); m_reportPage->setPos(20,20); m_reportView->centerOn(0,0); return; } void ReportWidget::setReportDataModels( const QList &models ) { m_reportdatamodels = models; } ReportData *ReportWidget::createReportData( const QDomElement &element ) { // get the data source QDomElement e = element.firstChildElement( "data-source" ); QString modelname = e.attribute( "select-from" ); return createReportData( modelname ); } ReportData *ReportWidget::createReportData( const QString &type ) { ReportData *r = Report::findReportData( m_reportdatamodels, type ); Q_ASSERT( r ); if ( r ) { r = r->clone(); r->setParent( this ); r->setProject( project() ); r->setScheduleManager( m_schedulemanager ); } return r; } bool ReportWidget::loadXML( const QDomDocument &doc ) { m_design = doc; slotRefreshView(); return true; } bool ReportWidget::loadContext( const KoXmlElement &/*context*/ ) { return true; } void ReportWidget::saveContext( QDomElement &/*context*/ ) const { } bool ReportWidget::documentIsNull() const { return m_design.isNull(); } //------------------ ReportNavigator::ReportNavigator( QWidget *parent ) : QWidget( parent ) { setupUi( this ); ui_first->setIcon(koIcon("go-first-view")); ui_last->setIcon(koIcon("go-last-view")); ui_prev->setIcon(koIcon("go-previous-view")); ui_next->setIcon(koIcon("go-next-view")); connect( ui_max, SIGNAL(valueChanged(int)), SLOT(slotMaxChanged(int))); connect( ui_selector, SIGNAL(valueChanged(int)), SLOT(setButtonsEnabled()) ); ui_max->setValue( 1 ); } void ReportNavigator::setMaximum( int value ) { ui_max->setMaximum( value ); ui_max->setValue( value ); } void ReportNavigator::setCurrentPage( int page ) { ui_selector->setValue( page ); } void ReportNavigator::slotMaxChanged( int value ) { ui_selector->setMaximum( value ); setButtonsEnabled(); } void ReportNavigator::setButtonsEnabled() { bool backw = ui_selector->value() > ui_selector->minimum(); ui_first->setEnabled( backw ); ui_prev->setEnabled( backw ); bool forw = ui_selector->value() < ui_selector->maximum(); ui_last->setEnabled( forw ); ui_next->setEnabled( forw ); } //---------------------------- ReportDesignDialog::ReportDesignDialog( QWidget *parent ) : KDialog( parent ), m_view( 0 ) { setCaption( i18nc( "@title:window", "Report Designer" ) ); m_panel = new ReportDesignPanel( this ); setMainWidget( m_panel ); } ReportDesignDialog::ReportDesignDialog( const QDomElement &element, const QList &models, QWidget *parent ) : KDialog( parent ), m_view( 0 ) { setCaption( i18nc( "@title:window", "Report Designer" ) ); setButtons( KDialog::Close | KDialog::User1 | KDialog::User2 ); setButtonText( KDialog::User1, i18n( "Save To View" ) ); setButtonIcon(KDialog::User1, koIcon("window-new")); setButtonText( KDialog::User2, i18n( "Save To File" ) ); setButtonIcon(KDialog::User2, koIcon("document-save-as")); m_panel = new ReportDesignPanel( element, models, this ); setMainWidget( m_panel ); connect( this, SIGNAL(user1Clicked()), SLOT(slotSaveToView()) ); connect( this, SIGNAL(user2Clicked()), SLOT(slotSaveToFile()) ); } void ReportDesignDialog::closeEvent ( QCloseEvent * e ) { if ( m_panel->m_modified ) { //NOTE: When the close (x) button in the window frame is clicked, QWidget automatically hides us if we don't handle it KPushButton *b = button( KDialog::Close ); if ( b ) { b->animateClick(); e->ignore(); return; } } KDialog::closeEvent ( e ); } void ReportDesignDialog::slotButtonClicked( int button ) { if ( button == KDialog::Close ) { if ( m_panel->m_modified ) { int res = KMessageBox::warningContinueCancel( this, i18nc( "@info", "The report definition has been modified.
" "If you continue, the modifications will be lost." ) ); if ( res == KMessageBox::Cancel ) { return; } } hide(); reject(); return; } KDialog::slotButtonClicked( button ); } void ReportDesignDialog::slotSaveToFile() { QPointer dialog = new KFileDialog(KUrl(), QString(), this); dialog->exec(); if ( ! dialog ) { return; } KUrl url(dialog->selectedUrl()); delete dialog; if (url.isEmpty()) { return; } if ( ! url.isLocalFile() ) { KMessageBox::sorry( this, i18n( "Can only save to a local file." ) ); return; } QFile file( url.toLocalFile() ); if ( ! file.open( QIODevice::WriteOnly ) ) { KMessageBox::sorry( this, i18nc( "@info", "Cannot open file:
%1", file.fileName() ) ); return; } QTextStream out( &file ); out << document().toString(); m_panel->m_modified = false; file.close(); } void ReportDesignDialog::slotSaveToView() { if ( m_view == 0 ) { emit createReportView( this ); return; } if ( m_panel->m_modified ) { saveToView(); } } void ReportDesignDialog::slotViewCreated( ViewBase *view ) { ReportView *v = dynamic_cast( view ); if ( v ) { m_view = v; saveToView(); // always save } } void ReportDesignDialog::saveToView() { if ( m_view == 0 ) { return; } KUndo2Command *cmd = new ModifyReportDefinitionCmd( m_view, document(), kundo2_i18n( "Modify report definition" ) ); emit modifyReportDefinition( cmd ); m_panel->m_modified = false; } QDomDocument ReportDesignDialog::document() const { return m_panel->document(); } //---- ReportDesignPanel::ReportDesignPanel( QWidget *parent ) : QWidget( parent ), m_modified( false ), m_reportdatamodels( Report::createBaseReportDataModels( this ) ) { QVBoxLayout *l = new QVBoxLayout( this ); KToolBar *tb = new KToolBar( this ); l->addWidget( tb ); QSplitter *sp1 = new QSplitter( this ); l->addWidget( sp1 ); QFrame *frame = new QFrame( sp1 ); frame->setFrameShadow( QFrame::Sunken ); frame->setFrameShape( QFrame::StyledPanel ); l = new QVBoxLayout( frame ); m_sourceeditor = new ReportSourceEditor( frame ); l->addWidget( m_sourceeditor ); QStandardItemModel *model = createSourceModel( m_sourceeditor ); m_sourceeditor->setModel( model ); m_propertyeditor = new KoProperty::EditorView( frame ); l->addWidget( m_propertyeditor ); QScrollArea *sa = new QScrollArea( sp1 ); m_designer = new KoReportDesigner( sa ); sa->setWidget( m_designer ); m_designer->setReportData( createReportData( m_sourceeditor->selectFromTag() ) ); slotPropertySetChanged(); connect( m_sourceeditor, SIGNAL(selectFromChanged(QString)), SLOT(setReportData(QString)) ); connect( this, SIGNAL(insertItem(QString)), m_designer, SLOT(slotItem(QString)) ); connect( m_designer, SIGNAL(propertySetChanged()), SLOT(slotPropertySetChanged()) ); connect( m_designer, SIGNAL(dirty()), SLOT(setModified()) ); connect( m_designer, SIGNAL(itemInserted(QString)), this, SLOT(slotItemInserted(QString))); populateToolbar( tb ); } ReportDesignPanel::ReportDesignPanel( const QDomElement &element, const QList &models, QWidget *parent ) : QWidget( parent ), m_modified( false ), m_reportdatamodels( models ) { QVBoxLayout *l = new QVBoxLayout( this ); KToolBar *tb = new KToolBar( this ); l->addWidget( tb ); QSplitter *sp1 = new QSplitter( this ); l->addWidget( sp1 ); QFrame *frame = new QFrame( sp1 ); frame->setFrameShadow( QFrame::Sunken ); frame->setFrameShape( QFrame::StyledPanel ); l = new QVBoxLayout( frame ); m_sourceeditor = new ReportSourceEditor( frame ); l->addWidget( m_sourceeditor ); QStandardItemModel *model = createSourceModel( m_sourceeditor ); m_sourceeditor->setModel( model ); m_sourceeditor->setSourceData( element.firstChildElement( "data-source" ) ); m_propertyeditor = new KoProperty::EditorView( frame ); l->addWidget( m_propertyeditor ); QScrollArea *sa = new QScrollArea( sp1 ); QDomElement e = element.firstChildElement( "report:content" ); if ( e.isNull() ) { m_designer = new KoReportDesigner( sa ); } else { m_designer = new KoReportDesigner( sa, e ); } sa->setWidget( m_designer ); m_designer->setReportData( createReportData( m_sourceeditor->selectFromTag() ) ); slotPropertySetChanged(); connect( m_sourceeditor, SIGNAL(selectFromChanged(QString)), SLOT(setReportData(QString)) ); connect( this, SIGNAL(insertItem(QString)), m_designer, SLOT(slotItem(QString)) ); connect( m_designer, SIGNAL(propertySetChanged()), SLOT(slotPropertySetChanged()) ); connect( m_designer, SIGNAL(dirty()), SLOT(setModified()) ); connect( m_designer, SIGNAL(itemInserted(QString)), this, SLOT(slotItemInserted(QString))); populateToolbar( tb ); } void ReportDesignPanel::populateToolbar( KToolBar *tb ) { tb->setToolButtonStyle( Qt::ToolButtonTextUnderIcon ); KAction *a = 0; a = KStandardAction::cut( this ); connect(a, SIGNAL(activated()), m_designer, SLOT(slotEditCut())); tb->addAction( a ); a = KStandardAction::copy( this ); connect(a, SIGNAL(activated()), m_designer, SLOT(slotEditCopy())); tb->addAction( a ); a = KStandardAction::paste( this ); connect(a, SIGNAL(activated()), m_designer, SLOT(slotEditPaste())); tb->addAction( a ); const KGuiItem del = KStandardGuiItem::del(); a = new KAction( del.icon(), del.text(), this ); a->setToolTip( del.toolTip() ); a->setShortcut( QKeySequence::Delete ); connect(a, SIGNAL(activated()), m_designer, SLOT(slotEditDelete())); tb->addAction( a ); tb->addSeparator(); a = new KAction(koIcon("arrow-up"), i18n("Raise"), this); connect(a, SIGNAL(activated()), m_designer, SLOT(slotRaiseSelected())); tb->addAction( a ); a = new KAction(koIcon("arrow-down"), i18n("Lower"), this); connect(a, SIGNAL(activated()), m_designer, SLOT(slotLowerSelected())); tb->addAction( a ); tb->addSeparator(); a = new KAction(koIcon("document-properties"), i18n("Section Editor"), this); a->setObjectName("sectionedit"); tb->addAction( a ); tb->addSeparator(); m_actionGroup = new QActionGroup(tb); // allow only the following item types, there is not appropriate data for others const QStringList itemtypes = QStringList() << "label" << "field" << "text" << "check" << "report:line" << "chart" << "web" << ""; //separator foreach( QAction *a, m_designer->actions(m_actionGroup) ) { if ( ! itemtypes.contains( a->objectName() ) ) { m_actionGroup->removeAction( a ); continue; } tb->addAction( a ); connect( a, SIGNAL(triggered(bool)), SLOT(slotInsertAction()) ); } } void ReportDesignPanel::slotPropertySetChanged() { kDebug(planDbg())<changeSet( m_designer->itemPropertySet() ); } } void ReportDesignPanel::slotInsertAction() { emit insertItem( sender()->objectName() ); } void ReportDesignPanel::slotItemInserted(const QString &) { if (m_actionGroup->checkedAction()) { m_actionGroup->checkedAction()->setChecked(false); } } void ReportDesignPanel::setReportData( const QString &tag ) { m_designer->setReportData( createReportData( tag ) ); } QDomDocument ReportDesignPanel::document() const { QDomDocument document( "planreportdefinition" ); document.appendChild( document.createProcessingInstruction( "xml", "version=\"1.0\" encoding=\"UTF-8\"" ) ); QDomElement e = document.createElement( "planreportdefinition" ); e.setAttribute( "editor", "Plan" ); e.setAttribute( "mime", "application/x-vnd.kde.plan.report.definition" ); e.setAttribute( "version", "1.0" ); document.appendChild( e ); if ( m_sourceeditor ) { m_sourceeditor->sourceData( e ); } e.appendChild( m_designer->document() ); /* kDebug(planDbg())<<"ReportDesignerView::document:"; kDebug(planDbg())<setData( "tasks", Reports::TagRole ); item->setEditable( false ); m->appendRow( item ); item = new QStandardItem( i18n( "Task status" ) ); item->setData( "taskstatus", Reports::TagRole ); item->setEditable( false ); m->appendRow( item ); item = new QStandardItem( i18n( "Resource assignments" ) ); item->setData( "resourceassignments", Reports::TagRole ); item->setEditable( false ); m->appendRow( item ); item = new QStandardItem( i18n( "Resources" ) ); item->setData( "resources", Reports::TagRole ); item->setEditable( false ); m->appendRow( item ); return m; } //------------------- ModifyReportDefinitionCmd ::ModifyReportDefinitionCmd( ReportView *view, const QDomDocument &value, const KUndo2MagicString& name ) : NamedCommand( name ), m_view( view ), m_newvalue( value.cloneNode().toDocument() ), m_oldvalue( m_view->document().cloneNode().toDocument() ) { } void ModifyReportDefinitionCmd ::execute() { m_view->loadXML( m_newvalue ); } void ModifyReportDefinitionCmd ::unexecute() { m_view->loadXML( m_oldvalue ); } //-------------------------- ReportDesigner::ReportDesigner(KoPart *part, KoDocument *doc, QWidget *parent) : ViewBase(part, doc, parent), m_designer( 0 ), m_reportdatamodels( Report::createBaseReportDataModels() ), m_groupsectioneditor( new GroupSectionEditor( this ) ) { QVBoxLayout *l = new QVBoxLayout( this ); l->setMargin(0); m_scrollarea = new QScrollArea( this ); l->addWidget( m_scrollarea ); setupGui(); QDomDocument domdoc; domdoc.setContent( QString( "" "" "" "Report" "" "" ) ); setData( domdoc ); } void ReportDesigner::setupGui() { /*KActionCollection *coll = actionCollection();*/ KAction *a = 0; QString name = "edit_copypaste"; a = KStandardAction::cut( this ); connect(a, SIGNAL(activated()), this, SIGNAL(cutActivated())); addAction( name, a ); a = KStandardAction::copy( this ); connect(a, SIGNAL(activated()), this, SIGNAL(copyActivated())); addAction( name, a ); a = KStandardAction::paste( this ); connect(a, SIGNAL(activated()), this, SIGNAL(pasteActivated())); addAction( name, a ); const KGuiItem del = KStandardGuiItem::del(); a = new KAction(del.icon(), del.text(), this); a->setObjectName( "edit_delete" ); a->setToolTip(del.toolTip()); a->setShortcut( QKeySequence::Delete ); connect(a, SIGNAL(activated()), this, SIGNAL(deleteActivated())); addAction( name, a ); name = "reportdesigner_list"; a = new KAction(koIcon("go-previous-view"), i18n("View report"), this); a->setObjectName( "view_report" ); connect(a, SIGNAL(activated()), SIGNAL(viewReport())); addAction( name, a ); m_undoaction = new KAction(koIcon("edit-undo"), i18n("Undo all changes"), this); m_undoaction->setObjectName( "undo_all_changes" ); m_undoaction->setEnabled( false ); connect(m_undoaction, SIGNAL(activated()), SLOT(undoAllChanges())); addAction( name, m_undoaction ); createDockers(); } void ReportDesigner::undoAllChanges() { if ( isModified() ) { setData(); } } void ReportDesigner::slotModified() { m_undoaction->setEnabled( isModified() ); } bool ReportDesigner::isModified() const { return m_designer->isModified(); } void ReportDesigner::setModified( bool on ) { m_designer->setModified( on ); m_undoaction->setEnabled( on ); } void ReportDesigner::setData( const QDomDocument &doc ) { m_original = doc.cloneNode().toDocument(); setData(); } void ReportDesigner::setData() { delete m_designer; QDomElement e = m_original.documentElement().firstChildElement( "report:content" ); if ( e.isNull() ) { m_designer = new KoReportDesigner( m_scrollarea ); } else { m_designer = new KoReportDesigner( m_scrollarea, e ); } m_scrollarea->setWidget( m_designer ); m_sourceeditor->setSourceData( m_original.documentElement().firstChildElement( "data-source" ) ); blockSignals( true ); setReportData( m_sourceeditor->selectFromTag() ); blockSignals( false ); slotPropertySetChanged(); connect(m_designer, SIGNAL(dirty()), SLOT(slotModified())); connect(m_designer, SIGNAL(propertySetChanged()), SLOT(slotPropertySetChanged())); connect(m_designer, SIGNAL(itemInserted(QString)), this, SLOT(slotItemInserted(QString))); connect(this, SIGNAL(cutActivated()), m_designer, SLOT(slotEditCut())); connect(this, SIGNAL(copyActivated()), m_designer, SLOT(slotEditCopy())); connect(this, SIGNAL(pasteActivated()), m_designer, SLOT(slotEditPaste())); connect(this, SIGNAL(deleteActivated()), m_designer, SLOT(slotEditDelete())); m_designer->setModified( false ); slotModified(); } QDomDocument ReportDesigner::document() const { QDomDocument document( "planreportdefinition" ); document.appendChild( document.createProcessingInstruction( "xml", "version=\"1.0\" encoding=\"UTF-8\"" ) ); QDomElement e = document.createElement( "planreportdefinition" ); e.setAttribute( "editor", "Plan" ); e.setAttribute( "mime", "application/x-vnd.kde.plan.report.definition" ); e.setAttribute( "version", "1.0" ); document.appendChild( e ); if ( m_sourceeditor ) { m_sourceeditor->sourceData( e ); } e.appendChild( m_designer->document() ); /* kDebug(planDbg())<<"ReportDesignerView::document:"; * kDebug(planDbg())<setLocation( Qt::LeftDockWidgetArea ); w = new QWidget( dw ); Ui::ReportToolsWidget tw; tw.setupUi( w ); // allow only the following item types, there is not appropriate data for others const QStringList itemtypes = QStringList() << "label" << "field" << "text" << "check" << "report:line" << "chart" << "web"; QActionGroup *ag = new QActionGroup( this ); int i = 0; foreach( QAction *a, m_designer->actions( ag ) ) { if ( itemtypes.contains( a->objectName() ) ) { QToolButton *tb = new QToolButton( w ); tb->setObjectName( a->objectName() ); tb->setIcon( a->icon() ); tb->setText( a->text() ); if ( tb->objectName() == "web" ) { tb->setToolTip( i18nc( "@into:tooltip", "Rich text" ) ); } else { tb->setToolTip( a->toolTip() ); } tb->setCheckable( true ); tw.horizontalLayout->insertWidget( i++, tb ); connect(tb, SIGNAL(clicked(bool)), SLOT(slotInsertAction())); connect(this, SIGNAL(resetButtonState(bool)), tb, SLOT(setChecked(bool))); } } m_sourceeditor = tw.sourceEditor; m_sourceeditor->setModel( createSourceModel( m_sourceeditor ) ); connect(m_sourceeditor, SIGNAL(selectFromChanged(QString)), SLOT(setReportData(QString))); m_propertyeditor = tw.propertyEditor; dw->setWidget( w ); addDocker( dw ); dw = new DockWidget( this, "Sections", i18nc( "@title:window report section docker", "Headers && Footers" ) ); dw->setLocation( Qt::RightDockWidgetArea ); w = new QScrollArea( dw ); Ui::ReportSectionsWidget sw; sw.setupUi( w ); dw->setWidget( w ); connect(sw.reportheader, SIGNAL(toggled(bool)), this, SLOT(slotSectionToggled(bool))); connect(sw.reportfooter, SIGNAL(toggled(bool)), this, SLOT(slotSectionToggled(bool))); connect(sw.headerFirstpage, SIGNAL(toggled(bool)), this, SLOT(slotSectionToggled(bool))); connect(sw.headerLastpage, SIGNAL(toggled(bool)), this, SLOT(slotSectionToggled(bool))); connect(sw.headerOddpages, SIGNAL(toggled(bool)), this, SLOT(slotSectionToggled(bool))); connect(sw.headerEvenpages, SIGNAL(toggled(bool)), this, SLOT(slotSectionToggled(bool))); connect(sw.headerAllpages, SIGNAL(toggled(bool)), this, SLOT(slotSectionToggled(bool))); connect(sw.footerFirstpage, SIGNAL(toggled(bool)), this, SLOT(slotSectionToggled(bool))); connect(sw.footerLastpage, SIGNAL(toggled(bool)), this, SLOT(slotSectionToggled(bool))); connect(sw.footerOddpages, SIGNAL(toggled(bool)), this, SLOT(slotSectionToggled(bool))); connect(sw.footerEvenpages, SIGNAL(toggled(bool)), this, SLOT(slotSectionToggled(bool))); connect(sw.footerAllpages, SIGNAL(toggled(bool)), this, SLOT(slotSectionToggled(bool))); addDocker( dw ); dw = new DockWidget( this, "Groups", i18nc( "@title:window report group section docker", "Groups" ) ); dw->setLocation( Qt::RightDockWidgetArea ); w = new QWidget( dw ); m_groupsectioneditor->setupUi( w ); dw->setWidget( w ); addDocker( dw ); } void ReportDesigner::setReportData( const QString &tag ) { ReportData *rd = Report::findReportData( m_reportdatamodels, tag ); if ( rd != m_designer->reportData() ) { emit optionsModified(); } m_designer->setReportData( rd ); m_groupsectioneditor->setData( m_designer, rd ); } QStandardItemModel *ReportDesigner::createSourceModel( QObject *parent ) const { QStandardItemModel *m = new QStandardItemModel( parent ); foreach ( ReportData *r, m_reportdatamodels ) { if ( r->isMainDataSource() ) { QStandardItem *item = new QStandardItem( r->sourceName() ); item->setData( r->objectName(), Reports::TagRole ); item->setEditable( false ); m->appendRow( item ); } } return m; } void ReportDesigner::slotPropertySetChanged() { if ( m_propertyeditor ) { m_propertyeditor->changeSet( m_designer->itemPropertySet() ); } } void ReportDesigner::slotInsertAction() { m_designer->slotItem( sender()->objectName() ); } void ReportDesigner::slotItemInserted(const QString &) { emit resetButtonState( false ); } void ReportDesigner::slotSectionToggled( bool on ) { QString n = sender()->objectName(); if ( n == "reportheader" ) { on ? m_designer->insertSection( KRSectionData::ReportHeader ) : m_designer->removeSection( KRSectionData::ReportHeader ); } else if ( n == "reportfooter" ) { on ? m_designer->insertSection( KRSectionData::ReportFooter ) : m_designer->removeSection( KRSectionData::ReportFooter ); } else if ( n == "headerFirstpage" ) { on ? m_designer->insertSection( KRSectionData::PageHeaderFirst ) : m_designer->removeSection( KRSectionData::PageHeaderFirst ); } else if ( n == "headerLastpage" ) { on ? m_designer->insertSection( KRSectionData::PageHeaderLast ) : m_designer->removeSection( KRSectionData::PageHeaderLast ); } else if ( n == "headerOddpages" ) { on ? m_designer->insertSection( KRSectionData::PageHeaderOdd ) : m_designer->removeSection( KRSectionData::PageHeaderOdd ); } else if ( n == "headerEvenpages" ) { on ? m_designer->insertSection( KRSectionData::PageHeaderEven ) : m_designer->removeSection( KRSectionData::PageHeaderEven ); } else if ( n == "headerAllpages" ) { on ? m_designer->insertSection( KRSectionData::PageHeaderAny ) : m_designer->removeSection( KRSectionData::PageHeaderAny ); } else if ( n == "footerFirstpage" ) { on ? m_designer->insertSection( KRSectionData::PageFooterFirst ) : m_designer->removeSection( KRSectionData::PageFooterFirst ); } else if ( n == "footerLastpage" ) { on ? m_designer->insertSection( KRSectionData::PageFooterLast ) : m_designer->removeSection( KRSectionData::PageFooterLast ); } else if ( n == "footerOddpages" ) { on ? m_designer->insertSection( KRSectionData::PageFooterOdd ) : m_designer->removeSection( KRSectionData::PageFooterOdd ); } else if ( n == "footerEvenpages" ) { on ? m_designer->insertSection( KRSectionData::PageFooterEven ) : m_designer->removeSection( KRSectionData::PageFooterEven ); } else if ( n == "footerAllpages" ) { on ? m_designer->insertSection( KRSectionData::PageFooterAny ) : m_designer->removeSection( KRSectionData::PageFooterAny ); } else { kDebug(planDbg())<<"unknown section"; } } bool ReportDesigner::loadContext(const KoXmlElement& context) { KoXmlElement e = context.namedItem( "planreportdefinition" ).toElement(); if ( e.isNull() ) { e = context.namedItem( "kplatoreportdefinition" ).toElement(); } if ( ! e.isNull() ) { QDomDocument doc( "context" ); KoXml::asQDomElement( doc, e ); setData( doc ); } else { kDebug(planDbg())<<"Invalid context xml"; setData( QDomDocument() ); // create an empty designer } return true; } void ReportDesigner::saveContext(QDomElement& context) const { context.appendChild( document().documentElement().cloneNode() ); } //--------------------- GroupSectionEditor::GroupSectionEditor( QObject *parent ) : QObject( parent ), designer( 0 ), reportdata( 0 ) { clear(); } void GroupSectionEditor::setupUi( QWidget *widget ) { gsw.setupUi( widget ); gsw.view->setModel( &model ); gsw.view->setItemDelegateForColumn( 0, new EnumDelegate( gsw.view ) ); gsw.view->setItemDelegateForColumn( 1, new CheckStateItemDelegate( gsw.view ) ); gsw.view->setItemDelegateForColumn( 2, new EnumDelegate( gsw.view ) ); gsw.view->setItemDelegateForColumn( 3, new EnumDelegate( gsw.view ) ); gsw.view->setItemDelegateForColumn( 4, new EnumDelegate( gsw.view ) ); gsw.btnAdd->setIcon(koIcon("list-add")); gsw.btnRemove->setIcon(koIcon("list-remove")); gsw.btnMoveUp->setIcon(koIcon("arrow-up")); gsw.btnMoveDown->setIcon(koIcon("arrow-down")); gsw.btnRemove->setEnabled( false ); gsw.btnMoveUp->setEnabled( false ); gsw.btnMoveDown->setEnabled( false ); connect(gsw.view->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), SLOT(slotSelectionChanged(QItemSelection))); connect(gsw.btnAdd, SIGNAL(clicked(bool)), SLOT(slotAddRow())); connect(gsw.btnRemove, SIGNAL(clicked(bool)), SLOT(slotRemoveRows())); connect(gsw.btnMoveUp, SIGNAL(clicked(bool)), SLOT(slotMoveRowUp())); connect(gsw.btnMoveDown, SIGNAL(clicked(bool)), SLOT(slotMoveRowDown())); } void GroupSectionEditor::slotSelectionChanged( const QItemSelection &sel ) { QItemSelectionModel *m = gsw.view->selectionModel(); gsw.btnRemove->setEnabled( ! sel.isEmpty() ); gsw.btnMoveUp->setEnabled( ! sel.isEmpty() && ! m->isRowSelected( 0, QModelIndex() ) ); gsw.btnMoveDown->setEnabled( ! sel.isEmpty() && ! m->isRowSelected( model.rowCount() - 1, QModelIndex() ) ); } void GroupSectionEditor::clear() { model.clear(); QStringList n; n << i18nc( "@title:column", "Column" ) << i18nc( "@title:column", "Sort" ) << i18nc( "@title:column", "Header" ) << i18nc( "@title:column", "Footer" ) << i18nc( "@title:column", "Page Break" ); model.setHorizontalHeaderLabels( n ); model.setHeaderData( 0, Qt::Horizontal, i18nc( "@info:tooltip", "Groups data by the selected column" ), Qt::ToolTipRole ); model.setHeaderData( 1, Qt::Horizontal, i18nc( "@info:tooltip", "Sorts data" ), Qt::ToolTipRole ); model.setHeaderData( 2, Qt::Horizontal, i18nc( "@info:tooltip", "Show header section" ), Qt::ToolTipRole ); model.setHeaderData( 3, Qt::Horizontal, i18nc( "@info:tooltip", "Show footer section" ), Qt::ToolTipRole ); model.setHeaderData( 4, Qt::Horizontal, i18nc( "@info:tooltip", "Insert page break" ), Qt::ToolTipRole ); } void GroupSectionEditor::setData( KoReportDesigner *d, ReportData *rd ) { clear(); designer = d; reportdata = rd; ReportSectionDetail *sd = designer->detailSection(); if ( ! sd ) { return; } for (int i = 0; i < sd->groupSectionCount(); i++) { ReportSectionDetailGroup *g = sd->groupSection( i ); ColumnItem *ci = new ColumnItem( g ); ci->names = rd->fieldNames(); ci->keys = rd->fieldKeys(); SortItem *si = new SortItem( g ); HeaderItem *hi = new HeaderItem( g ); FooterItem *fi = new FooterItem( g ); PageBreakItem *pi = new PageBreakItem( g ); model.appendRow( QList() << ci << si << hi << fi << pi ); } } void GroupSectionEditor::slotAddRow() { ReportSectionDetail *sd = designer->detailSection(); if ( ! sd ) { return; } ReportSectionDetailGroup * g = new ReportSectionDetailGroup( reportdata->fieldKeys().value( 0 ), sd, sd ); sd->insertGroupSection( sd->groupSectionCount(), g ); ColumnItem *ci = new ColumnItem( g ); ci->names = reportdata->fieldNames(); ci->keys = reportdata->fieldKeys(); SortItem *si = new SortItem( g ); HeaderItem *hi = new HeaderItem( g ); FooterItem *fi = new FooterItem( g ); PageBreakItem *pi = new PageBreakItem( g ); model.appendRow( QList() << ci << si << hi << fi << pi ); } void GroupSectionEditor::slotRemoveRows() { ReportSectionDetail *sd = designer->detailSection(); if ( ! sd ) { return; } QList rows; foreach ( const QModelIndex &idx, gsw.view->selectionModel()->selectedRows() ) { rows <= 0; --i ) { int row = rows.at( i ); QList items = model.takeRow( row ); sd->removeGroupSection( row, true ); qDeleteAll( items ); } } void GroupSectionEditor::slotMoveRowUp() { ReportSectionDetail *sd = designer->detailSection(); if ( ! sd ) { return; } QList rows; foreach ( const QModelIndex &idx, gsw.view->selectionModel()->selectedRows() ) { rows < items = model.takeRow( row ); ReportSectionDetailGroup *g = sd->groupSection( row ); bool showgh = g->groupHeaderVisible(); bool showgf = g->groupFooterVisible(); sd->removeGroupSection( row ); sd->insertGroupSection( row - 1, g ); g->setGroupHeaderVisible( showgh ); g->setGroupFooterVisible( showgf ); model.insertRow( row - 1, items ); } QModelIndex idx1 = model.index( rows.first()-1, 0 ); QModelIndex idx2 = model.index( rows.last()-1, 0 ); QItemSelection s = QItemSelection ( idx1, idx2 ); gsw.view->selectionModel()->select( s, QItemSelectionModel::Rows | QItemSelectionModel::Clear | QItemSelectionModel::Select ); } void GroupSectionEditor::slotMoveRowDown() { ReportSectionDetail *sd = designer->detailSection(); if ( ! sd ) { return; } QList rows; foreach ( const QModelIndex &idx, gsw.view->selectionModel()->selectedRows() ) { rows <= model.rowCount() - 1 ) { return; } for ( int i = rows.count() - 1; i >= 0; --i ) { int row = rows.at( i ); QList items = model.takeRow( row ); ReportSectionDetailGroup *g = sd->groupSection( row ); bool showgh = g->groupHeaderVisible(); bool showgf = g->groupFooterVisible(); sd->removeGroupSection( row ); sd->insertGroupSection( row + 1, g ); g->setGroupHeaderVisible( showgh ); g->setGroupFooterVisible( showgf ); model.insertRow( row + 1, items ); } QModelIndex idx1 = model.index( rows.first()+1, 0 ); QModelIndex idx2 = model.index( rows.last()+1, 0 ); QItemSelection s = QItemSelection ( idx1, idx2 ); gsw.view->selectionModel()->select( s, QItemSelectionModel::Rows | QItemSelectionModel::Clear | QItemSelectionModel::Select ); } //---------------- GroupSectionEditor::ColumnItem::ColumnItem( ReportSectionDetailGroup *g ) : Item( g ) { } QVariant GroupSectionEditor::ColumnItem::data( int role ) const { switch ( role ) { case Qt::DisplayRole: return names.value( keys.indexOf( group->column() ) ); case Role::EnumList: return names; case Role::EnumListValue: return keys.indexOf( group->column() ); default: break; } return Item::data( role ); } void GroupSectionEditor::ColumnItem::setData( const QVariant &value, int role ) { if ( role == Qt::EditRole ) { group->setColumn( keys.value( value.toInt() ) ); return; } return Item::setData( value, role ); } //--------------------- GroupSectionEditor::SortItem::SortItem( ReportSectionDetailGroup *g ) : Item( g ) { names << i18n( "Ascending" ) << i18n( "Descending" ); } QVariant GroupSectionEditor::SortItem::data( int role ) const { switch ( role ) { case Qt::DisplayRole: return QVariant(); case Qt::ToolTipRole: return group->sort() ? names.value( 1 ) : names.value( 0 ); case Qt::DecorationRole: return group->sort() ? koIcon("arrow-down") : koIcon("arrow-up"); case Qt::EditRole: return group->sort() ? Qt::Unchecked : Qt::Checked; case Role::EnumList: return names; case Role::EnumListValue: return group->sort() ? 1 : 0; default: break; } return Item::data( role ); } void GroupSectionEditor::SortItem::setData( const QVariant &value, int role ) { if ( role == Qt::EditRole ) { group->setSort( value.toInt() == 0 ? Qt::AscendingOrder : Qt::DescendingOrder ); return; } else if ( role == Qt::CheckStateRole ) { group->setSort( value.toInt() == 0 ? Qt::DescendingOrder : Qt::AscendingOrder ); return; } return Item::setData( value, role ); } //--------------------- GroupSectionEditor::HeaderItem::HeaderItem( ReportSectionDetailGroup *g ) : Item( g ) { names << i18n( "No" ) << i18n( "Yes" ); setCheckable( true ); } QVariant GroupSectionEditor::HeaderItem::data( int role ) const { switch ( role ) { case Qt::DisplayRole: return QVariant(); case Qt::CheckStateRole: return group->groupHeaderVisible() ? Qt::Checked : Qt::Unchecked; case Role::EnumList: return names; case Role::EnumListValue: return group->groupHeaderVisible() ? 1 : 0; default: break; } return Item::data( role ); } void GroupSectionEditor::HeaderItem::setData( const QVariant &value, int role ) { kDebug(planDbg())<setGroupHeaderVisible( value.toInt() == 1 ); return; } else if ( role == Qt::CheckStateRole ) { group->setGroupHeaderVisible( value.toInt() > 0 ); return; } return Item::setData( value, role ); } //--------------------- GroupSectionEditor::FooterItem::FooterItem( ReportSectionDetailGroup *g ) : Item( g ) { names << i18n( "No" ) << i18n( "Yes" ); setCheckable( true ); } QVariant GroupSectionEditor::FooterItem::data( int role ) const { switch ( role ) { case Qt::DisplayRole: return QVariant(); case Qt::CheckStateRole: return group->groupFooterVisible() ? Qt::Checked : Qt::Unchecked; case Role::EnumList: return names; case Role::EnumListValue: return group->groupFooterVisible() ? 1 : 0; default: break; } return Item::data( role ); } void GroupSectionEditor::FooterItem::setData( const QVariant &value, int role ) { if ( role == Qt::EditRole ) { group->setGroupFooterVisible( value.toInt() == 1 ); return; } else if ( role == Qt::CheckStateRole ) { group->setGroupFooterVisible( value.toInt() > 0 ); return; } return Item::setData( value, role ); } //--------------------- GroupSectionEditor::PageBreakItem::PageBreakItem( ReportSectionDetailGroup *g ) : Item( g ) { names << i18n( "None" ) << i18n( "After footer" ) << i18n( "Before header" ); } QVariant GroupSectionEditor::PageBreakItem::data( int role ) const { switch ( role ) { case Qt::DisplayRole: return names.value( (int)group->pageBreak() ); case Qt::ToolTipRole: return names.value( (int)group->pageBreak() ); case Role::EnumList: return names; case Role::EnumListValue: return (int)group->pageBreak(); default: break; } return Item::data( role ); } void GroupSectionEditor::PageBreakItem::setData( const QVariant &value, int role ) { if ( role == Qt::EditRole ) { group->setPageBreak( (ReportSectionDetailGroup::PageBreak)( value.toInt() ) ); return; } return Item::setData( value, role ); } } // namespace KPlato #include "reportview.moc" #include "reportview_p.moc" diff --git a/plan/libs/ui/reports/reportview.h b/plan/libs/ui/reports/reportview.h index de48435b1ab..632a803fdea 100644 --- a/plan/libs/ui/reports/reportview.h +++ b/plan/libs/ui/reports/reportview.h @@ -1,328 +1,327 @@ /* * KPlato Report Plugin * Copyright (C) 2007-2008 by Adam Pigg (adam@piggz.co.uk) * Copyright (C) 2010, 2011 by Dag Andersen * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser 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 KPLATOREPORTVIEW_H #define KPLATOREPORTVIEW_H #include "kplatoui_export.h" #include "kptviewbase.h" #include "kptsplitterview.h" #include "kptcommand.h" #include "ui_reportnavigator.h" #include #include class KoDocument; class KoShape; struct KoPageLayout; class KoReportPage; class KoReportPreRenderer; class ORODocument; class KoReportDesigner; class KoReportRendererBase; namespace KoProperty { class EditorView; class Set; } class QGraphicsView; class QGraphicsScene; class QDomElement; class QStackedWidget; class QDomElement; class QScrollArea; class QStandardItemModel; class KUndo2Command; namespace KPlato { class Project; class ReportView; class ReportWidget; class ReportDesigner; class ReportData; class ReportSourceEditor; class ReportNavigator; class ReportDesignPanel; class GroupSectionEditor; class ReportPrintingDialog : public KoPrintingDialog { Q_OBJECT public: ReportPrintingDialog( ViewBase *view, ORODocument *reportDocument ); ~ReportPrintingDialog(); void printPage( int page, QPainter &painter ); int documentLastPage() const; virtual QList createOptionWidgets() const { return QList(); } virtual QList shapesOnPage(int) { return QList(); } virtual QAbstractPrintDialog::PrintDialogOptions printDialogOptions() const; public Q_SLOTS: virtual void startPrinting(RemovePolicy removePolicy = DoNotDelete); protected: ORODocument *m_reportDocument; KoReportRendererContext m_context; KoReportRendererBase *m_renderer; }; //------------------- class KPLATOUI_EXPORT ReportView : public ViewBase { Q_OBJECT public: ReportView(KoPart *part, KoDocument *doc, QWidget *parent); void setProject( Project *project ); KoPrintJob *createPrintJob(); /// Load the design document @p doc bool loadXML( const QDomDocument &doc ); /// Loads context info into this view. virtual bool loadContext( const KoXmlElement &context ); /// Save context info from this view. virtual void saveContext( QDomElement &context ) const; ReportWidget *reportWidget() const; ReportDesigner *reportDesigner() const; QDomDocument document() const; QList reportDataModels() const; public Q_SLOTS: void setGuiActive( bool active ); void setScheduleManager( ScheduleManager *sm ); private Q_SLOTS: void slotEditReport(); void slotViewReport(); Q_SIGNALS: void editReportDesign( ReportWidget* ); private: QStackedWidget *m_stack; }; //------------------- class KPLATOUI_EXPORT ReportWidget : public ViewBase { Q_OBJECT public: ReportWidget(KoPart *part, KoDocument *doc, QWidget *parent); public Q_SLOTS: void setGuiActive( bool active ); void renderPage( int page ); /// Return true if document is null bool documentIsNull() const; /// Load the design document @p doc bool loadXML( const QDomDocument &doc ); /// Loads context info into this view. virtual bool loadContext( const KoXmlElement &context ); /// Save context info from this view. virtual void saveContext( QDomElement &context ) const; KoPrintJob *createPrintJob(); /// Return the page layout used for printing this view KoPageLayout pageLayout() const; void setReportDataModels( const QList &models ); QList reportDataModels() const { return m_reportdatamodels; } Q_SIGNALS: void editReportDesign(); public Q_SLOTS: /// refresh display void slotRefreshView(); protected: void setupGui(); private Q_SLOTS: void nextPage(); void prevPage(); void firstPage(); void lastPage(); void slotExport(); void slotExportFinished( int result ); private: ReportData *createReportData( const QDomElement &connection ); ReportData *createReportData( const QString &type ); void exportToOdtTable( KoReportRendererContext &context ); void exportToOdtFrames( KoReportRendererContext &context ); void exportToOds( KoReportRendererContext &context ); void exportToHtml( KoReportRendererContext &context ); void exportToXHtml( KoReportRendererContext &context ); private: KoReportPreRenderer *m_preRenderer; KoReportRendererFactory m_factory; - ORODocument *m_reportDocument; QGraphicsView *m_reportView; QGraphicsScene *m_reportScene; KoReportPage *m_reportPage; ReportNavigator *m_pageSelector; int m_currentPage; int m_pageCount; QList m_reportdatamodels; QDomDocument m_design; }; //----------------- class KPLATOUI_EXPORT ReportNavigator : public QWidget, public Ui::ReportNavigator { Q_OBJECT public: explicit ReportNavigator(QWidget *parent = 0); void setCurrentPage( int page ); public Q_SLOTS: void setMaximum( int ); protected Q_SLOTS: void slotMaxChanged( int ); void setButtonsEnabled(); }; class KPLATOUI_EXPORT ReportDesignDialog : public KDialog { Q_OBJECT public: explicit ReportDesignDialog( QWidget *parent = 0 ); ReportDesignDialog( const QDomElement &element, const QList &models, QWidget *parent = 0 ); QDomDocument document() const; Q_SIGNALS: void createReportView( ReportDesignDialog *dlg ); void modifyReportDefinition( KUndo2Command *cmd ); public Q_SLOTS: void slotViewCreated( ViewBase *view ); protected Q_SLOTS: void slotSaveToFile(); void slotSaveToView(); virtual void slotButtonClicked(int button); void closeEvent ( QCloseEvent * e ); protected: void saveToView(); private: ReportDesignPanel *m_panel; ReportView *m_view; }; //------------------- class KPLATOUI_EXPORT ModifyReportDefinitionCmd : public NamedCommand { public: ModifyReportDefinitionCmd( ReportView *view, const QDomDocument &value, const KUndo2MagicString &name = KUndo2MagicString() ); void execute(); void unexecute(); private: ReportView *m_view; QDomDocument m_newvalue; QDomDocument m_oldvalue; }; //------------------------- class KPLATOUI_EXPORT ReportDesigner : public ViewBase { Q_OBJECT public: ReportDesigner(KoPart *part, KoDocument *doc, QWidget *parent = 0); bool isModified() const; void setModified( bool on ); QDomDocument document() const; void setData( const QDomDocument &doc ); /// Loads context info into this view. virtual bool loadContext( const KoXmlElement &context ); /// Save context info from this view. virtual void saveContext( QDomElement &context ) const; public Q_SLOTS: void setReportData( const QString &tag ); Q_SIGNALS: void viewReport(); void resetButtonState( bool ); void raiseClicked(); void lowerClicked(); void cutActivated(); void copyActivated(); void pasteActivated(); void deleteActivated(); protected: void setupGui(); void createDockers(); QStandardItemModel *createSourceModel( QObject *parent ) const; void setData(); protected Q_SLOTS: void slotPropertySetChanged(); void slotInsertAction(); void slotItemInserted( const QString & ); void slotSectionToggled( bool ); void undoAllChanges(); void slotModified(); private: QScrollArea *m_scrollarea; KoReportDesigner *m_designer; ReportSourceEditor *m_sourceeditor; KoProperty::EditorView *m_propertyeditor; QList m_reportdatamodels; GroupSectionEditor *m_groupsectioneditor; QDomDocument m_original; QAction *m_undoaction; }; } // namespace KPlato #endif