diff --git a/src/plugins/forms/kexidatasourcepage.cpp b/src/plugins/forms/kexidatasourcepage.cpp index 3e4b23858..4b22dc11e 100644 --- a/src/plugins/forms/kexidatasourcepage.cpp +++ b/src/plugins/forms/kexidatasourcepage.cpp @@ -1,454 +1,463 @@ /* This file is part of the KDE project - Copyright (C) 2005-2009 Jarosław Staniek + Copyright (C) 2005-2017 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 "kexidatasourcepage.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include KexiDataSourcePage::KexiDataSourcePage(QWidget *parent) : KexiPropertyPaneViewBase(parent) , m_noDataSourceAvailableSingleText( xi18n("No data source could be assigned for this widget.") ) , m_noDataSourceAvailableMultiText( xi18n("No data source could be assigned for multiple widgets.") ) , m_insideClearFormDataSourceSelection(false) #ifndef KEXI_AUTOFIELD_FORM_WIDGET_SUPPORT , m_tableOrQuerySchema(0) #endif { infoLabel()->setContentsMargins(0, 0, 0, spacing()); m_noDataSourceAvailableLabel = new QLabel(m_noDataSourceAvailableSingleText, this); m_noDataSourceAvailableLabel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); m_noDataSourceAvailableLabel->setContentsMargins(0, 0, 0, spacing()); m_noDataSourceAvailableLabel->setAlignment(Qt::AlignBottom | Qt::AlignLeft); m_noDataSourceAvailableLabel->setWordWrap(true); mainLayout()->addWidget(m_noDataSourceAvailableLabel); //-Widget's Data Source QHBoxLayout *hlyr = new QHBoxLayout(); mainLayout()->addLayout(hlyr); #if 0 //! @todo unhide this when expression work // m_widgetDSLabel = new QLabel(futureI18nc("Table Field, Query Field or Expression", "Source field or expression"), this); #else m_widgetDSLabel = new QLabel( xi18nc("Table Field or Query Field", "Widget's data source:"), this); #endif m_widgetDSLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); m_widgetDSLabel->setAlignment(Qt::AlignLeft | Qt::AlignBottom); hlyr->addWidget(m_widgetDSLabel); mainLayout()->addSpacing(KexiUtils::spacingHint()); // needed because unlike m_dataSourceLabel we have no button in hlyr #if 0 m_clearWidgetDSButton = new KexiSmallToolButton( koIcon("edit-clear-locationbar-rtl"), QString(), this); m_clearWidgetDSButton->setObjectName("clearWidgetDSButton"); m_clearWidgetDSButton->setMinimumHeight(m_widgetDSLabel->minimumHeight()); m_clearWidgetDSButton->setToolTip(futureI18n("Clear widget's data source")); hlyr->addWidget(m_clearWidgetDSButton); connect(m_clearWidgetDSButton, SIGNAL(clicked()), this, SLOT(clearWidgetDataSourceSelection())); #endif m_widgetDataSourceCombo = new KexiFieldComboBox(this); m_widgetDataSourceCombo->setObjectName("sourceFieldCombo"); m_widgetDataSourceCombo->setContentsMargins(0, 0, 0, 0); m_widgetDSLabel->setBuddy(m_widgetDataSourceCombo); connect(m_widgetDataSourceCombo, SIGNAL(editTextChanged(QString)), this, SLOT(slotWidgetDataSourceTextChanged(QString))); mainLayout()->addWidget(m_widgetDataSourceCombo); m_widgetDataSourceComboSpacer = addWidgetSpacer(); //- Form's Data Source hlyr = new QHBoxLayout(); hlyr->setContentsMargins(0, 0, 0, 0); mainLayout()->addLayout(hlyr); m_dataSourceLabel = new QLabel(xi18n("Form's data source:"), this); m_dataSourceLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); m_dataSourceLabel->setAlignment(Qt::AlignLeft | Qt::AlignBottom); hlyr->addWidget(m_dataSourceLabel); m_gotoButton = new KexiSmallToolButton( koIcon("go-jump"), QString(), this); m_gotoButton->setObjectName("gotoButton"); m_gotoButton->setToolTip(xi18n("Go to selected form's data source")); m_gotoButton->setWhatsThis(xi18n("Goes to selected form's data source")); hlyr->addWidget(m_gotoButton); connect(m_gotoButton, SIGNAL(clicked()), this, SLOT(slotGotoSelected())); #if 0 m_clearDSButton = new KexiSmallToolButton( koIcon("edit-clear-locationbar-rtl"), QString(), this); m_clearDSButton->setObjectName("clearDSButton"); m_clearDSButton->setMinimumHeight(m_dataSourceLabel->minimumHeight()); m_clearDSButton->setToolTip(futureI18n("Clear form's data source")); hlyr->addWidget(m_clearDSButton); connect(m_clearDSButton, SIGNAL(clicked()), this, SLOT(clearFormDataSourceSelection())); #endif m_formDataSourceCombo = new KexiDataSourceComboBox(this); m_formDataSourceCombo->setObjectName("dataSourceCombo"); m_formDataSourceCombo->setContentsMargins(0, 0, 0, 0); m_dataSourceLabel->setBuddy(m_formDataSourceCombo); mainLayout()->addWidget(m_formDataSourceCombo); m_formDataSourceComboSpacer = addWidgetSpacer(); #ifndef KEXI_AUTOFIELD_FORM_WIDGET_SUPPORT mainLayout()->addStretch(); #else //2. Inserting fields //helper info //! @todo allow to hide such helpers by adding global option hlyr = new QHBoxLayout(); hlyr->setContentsMargins(0, 0, 0, 0); mainLayout()->addLayout(hlyr); m_mousePointerLabel = new QLabel(this); hlyr->addWidget(m_mousePointerLabel); m_mousePointerLabel->setPixmap(koIcon("tool-pointer")); m_mousePointerLabel->setFixedWidth(m_mousePointerLabel->pixmap() ? m_mousePointerLabel->pixmap()->width() : 0); m_availableFieldsDescriptionLabel = new QLabel( futureI18n("Select fields from the list below and drag them onto" " a form or click the Insert button"), this); m_availableFieldsDescriptionLabel->setAlignment(Qt::AlignLeft); m_availableFieldsDescriptionLabel->setWordWrap(true); hlyr->addWidget(m_availableFieldsDescriptionLabel); //Available Fields hlyr = new QHBoxLayout(); hlyr->setContentsMargins(0, 0, 0, 0); mainLayout()->addLayout(hlyr); m_availableFieldsLabel = new QLabel(futureI18n("Available fields"), this); m_availableFieldsLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); hlyr->addWidget(m_availableFieldsLabel); m_addField = new KexiSmallToolButton( KexiIcon("add-field"), futureI18nc("Insert selected field into form", "Insert"), this); m_addField->setObjectName("addFieldButton"); m_addField->setFocusPolicy(Qt::StrongFocus); m_addField->setToolTip(futureI18n("Insert selected fields into form")); m_addField->setWhatsThis(futureI18n("Inserts selected fields into form")); hlyr->addWidget(m_addField); connect(m_addField, SIGNAL(clicked()), this, SLOT(slotInsertSelectedFields())); m_fieldListView = new KexiFieldListView(this, KexiFieldListView::ShowDataTypes | KexiFieldListView::AllowMultiSelection); m_fieldListView->setObjectName("fieldListView"); m_fieldListView->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding)); m_availableFieldsLabel->setBuddy(m_fieldListView); mainLayout()->addWidget(m_fieldListView, 1); connect(m_fieldListView, SIGNAL(selectionChanged()), this, SLOT(slotFieldListViewSelectionChanged())); connect(m_fieldListView, SIGNAL(fieldDoubleClicked(QString,QString,QString)), this, SLOT(slotFieldDoubleClicked(QString,QString,QString))); #endif mainLayout()->addStretch(1); connect(m_formDataSourceCombo, SIGNAL(editTextChanged(QString)), this, SLOT(slotFormDataSourceTextChanged(QString))); connect(m_formDataSourceCombo, SIGNAL(dataSourceChanged()), this, SLOT(slotFormDataSourceChanged())); connect(m_widgetDataSourceCombo, SIGNAL(selected()), this, SLOT(slotFieldSelected())); clearFormDataSourceSelection(); slotFieldListViewSelectionChanged(); } KexiDataSourcePage::~KexiDataSourcePage() { #ifndef KEXI_AUTOFIELD_FORM_WIDGET_SUPPORT delete m_tableOrQuerySchema; #endif } void KexiDataSourcePage::setProject(KexiProject *prj) { m_widgetDataSourceCombo->setProject(prj); m_formDataSourceCombo->setProject(prj); } void KexiDataSourcePage::clearFormDataSourceSelection(bool alsoClearComboBox) { if (m_insideClearFormDataSourceSelection) return; m_insideClearFormDataSourceSelection = true; if (alsoClearComboBox && !m_formDataSourceCombo->selectedName().isEmpty()) m_formDataSourceCombo->setDataSource(QString(), QString()); m_gotoButton->setEnabled(false); m_widgetDataSourceCombo->setFieldOrExpression(QString()); #ifdef KEXI_AUTOFIELD_FORM_WIDGET_SUPPORT m_addField->setEnabled(false); m_fieldListView->clear(); #endif m_insideClearFormDataSourceSelection = false; } void KexiDataSourcePage::slotWidgetDataSourceTextChanged(const QString &text) { if (text.isEmpty()) { clearWidgetDataSourceSelection(); } } void KexiDataSourcePage::clearWidgetDataSourceSelection() { m_widgetDataSourceCombo->setFieldOrExpression(QString()); slotFieldSelected(); } void KexiDataSourcePage::slotGotoSelected() { const QString pluginId(m_formDataSourceCombo->selectedPluginId()); if (pluginId == "org.kexi-project.table" || pluginId == "org.kexi-project.query") { if (m_formDataSourceCombo->isSelectionValid()) emit jumpToObjectRequested(pluginId, m_formDataSourceCombo->selectedName()); } } void KexiDataSourcePage::slotInsertSelectedFields() { #ifdef KEXI_AUTOFIELD_FORM_WIDGET_SUPPORT QStringList selectedFieldNames(m_fieldListView->selectedFieldNames()); if (selectedFieldNames.isEmpty()) return; emit insertAutoFields(m_fieldListView->schema()->table() ? "org.kexi-project.table" : "org.kexi-project.query", m_fieldListView->schema()->name(), selectedFieldNames); #endif } void KexiDataSourcePage::slotFieldDoubleClicked(const QString& sourcePluginId, const QString& sourceName, const QString& fieldName) { #ifdef KEXI_AUTOFIELD_FORM_WIDGET_SUPPORT QStringList selectedFields; selectedFields.append(fieldName); emit insertAutoFields(sourcePluginId, sourceName, selectedFields); #else Q_UNUSED(sourcePluginId); Q_UNUSED(sourceName); Q_UNUSED(fieldName); #endif } void KexiDataSourcePage::slotFormDataSourceTextChanged(const QString &text) { const bool enable = m_formDataSourceCombo->isSelectionValid(); if (text.isEmpty()) { clearFormDataSourceSelection(); } else if (!enable) { clearFormDataSourceSelection(m_formDataSourceCombo->selectedName().isEmpty()/*alsoClearComboBox*/); } updateSourceFieldWidgetsAvailability(); } void KexiDataSourcePage::slotFormDataSourceChanged() { if (!m_formDataSourceCombo->project()) return; const QString pluginId(m_formDataSourceCombo->selectedPluginId()); bool dataSourceFound = false; QString name(m_formDataSourceCombo->selectedName()); const bool isIdAcceptable = pluginId == QLatin1String("org.kexi-project.table") || pluginId == QLatin1String("org.kexi-project.query"); if (isIdAcceptable && m_formDataSourceCombo->isSelectionValid()) { KDbTableOrQuerySchema *tableOrQuery = new KDbTableOrQuerySchema( m_formDataSourceCombo->project()->dbConnection(), name.toLatin1(), pluginId == "org.kexi-project.table" ? KDbTableOrQuerySchema::Type::Table : KDbTableOrQuerySchema::Type::Query); if (tableOrQuery->table() || tableOrQuery->query()) { #ifdef KEXI_AUTOFIELD_FORM_WIDGET_SUPPORT m_fieldListView->setSchema(tableOrQuery); #else m_tableOrQuerySchema = tableOrQuery; #endif dataSourceFound = true; m_widgetDataSourceCombo->setTableOrQuery(name, pluginId == "org.kexi-project.table"); } else { delete tableOrQuery; } } if (!dataSourceFound) { m_widgetDataSourceCombo->setTableOrQuery(QString(), true); } m_gotoButton->setEnabled(dataSourceFound); if (dataSourceFound) { slotFieldListViewSelectionChanged(); } else { #ifdef KEXI_AUTOFIELD_FORM_WIDGET_SUPPORT m_addField->setEnabled(false); #endif } updateSourceFieldWidgetsAvailability(); emit formDataSourceChanged(pluginId, name); } void KexiDataSourcePage::slotFieldSelected() { KDbField::Type dataType = KDbField::InvalidType; #ifdef KEXI_AUTOFIELD_FORM_WIDGET_SUPPORT //! @todo this should also work for expressions KDbField *field = m_fieldListView->schema()->field( m_widgetDataSourceCombo->fieldOrExpression()); #else KDbField *field = m_tableOrQuerySchema->field( m_widgetDataSourceCombo->fieldOrExpression()); //temp #endif if (field) dataType = field->type(); emit dataSourceFieldOrExpressionChanged( m_widgetDataSourceCombo->fieldOrExpression(), m_widgetDataSourceCombo->fieldOrExpressionCaption(), dataType ); } void KexiDataSourcePage::setFormDataSource(const QString& pluginId, const QString& name) { m_formDataSourceCombo->setDataSource(pluginId, name); } #define KexiDataSourcePage_FADE 1 void KexiDataSourcePage::assignPropertySet(KPropertySet* propertySet) { QString objectName; if (propertySet) objectName = propertySet->propertyValue("objectName").toString(); if (!objectName.isEmpty() && objectName == m_currentObjectName) return; //the same object m_currentObjectName = objectName; //! @todo #if KexiDataSourcePage_FADE KexiFadeWidgetEffect *animation = 0; if (isVisible()) animation = new KexiFadeWidgetEffect(this); #endif QString objectClassName; if (propertySet) { objectClassName = propertySet->propertyValue("this:className").toString(); } updateInfoLabelForPropertySet(propertySet); const bool isForm = objectClassName == "KexiDBForm"; const bool multipleSelection = objectClassName == "special:multiple"; const bool hasDataSourceProperty = propertySet && propertySet->contains("dataSource") && !multipleSelection; if (!isForm) { //this is a widget QString dataSource; if (hasDataSourceProperty) { if (propertySet) { dataSource = (*propertySet)["dataSource"].value().toString(); } m_noDataSourceAvailableLabel->hide(); m_widgetDataSourceCombo->setFieldOrExpression(dataSource); m_widgetDataSourceCombo->setEnabled(true); m_widgetDSLabel->show(); m_widgetDataSourceCombo->show(); m_widgetDataSourceComboSpacer->show(); updateSourceFieldWidgetsAvailability(); } } if (isForm) { m_noDataSourceAvailableLabel->hide(); } else if (!hasDataSourceProperty) { if (multipleSelection) { m_noDataSourceAvailableLabel->setText(m_noDataSourceAvailableMultiText); } else { m_noDataSourceAvailableLabel->setText(m_noDataSourceAvailableSingleText); } m_noDataSourceAvailableLabel->show(); m_widgetDataSourceCombo->setEditText(QString()); } if (isForm || !hasDataSourceProperty) { //no source field can be set m_widgetDSLabel->hide(); m_widgetDataSourceCombo->hide(); m_widgetDataSourceComboSpacer->hide(); } //! @todo #if KexiDataSourcePage_FADE if (animation) animation->start(100); #endif } void KexiDataSourcePage::slotFieldListViewSelectionChanged() { #ifdef KEXI_AUTOFIELD_FORM_WIDGET_SUPPORT //update "add field" button's state for (Q3ListViewItemIterator it(m_fieldListView); it.current(); ++it) { if (it.current()->isSelected()) { m_addField->setEnabled(true); return; } } m_addField->setEnabled(false); #endif } void KexiDataSourcePage::updateSourceFieldWidgetsAvailability() { const bool hasDataSource = m_formDataSourceCombo->isSelectionValid(); m_widgetDataSourceCombo->setEnabled(hasDataSource); m_widgetDSLabel->setEnabled(hasDataSource); #ifdef KEXI_AUTOFIELD_FORM_WIDGET_SUPPORT m_fieldListView->setEnabled(hasDataSource); m_availableFieldsLabel->setEnabled(hasDataSource); m_mousePointerLabel->setEnabled(hasDataSource); m_availableFieldsDescriptionLabel->setEnabled(hasDataSource); #endif } +QString KexiDataSourcePage::selectedPluginId() const +{ + return m_formDataSourceCombo->selectedPluginId(); +} + +QString KexiDataSourcePage::selectedName() const +{ + return m_formDataSourceCombo->selectedName(); +} diff --git a/src/plugins/forms/kexidatasourcepage.h b/src/plugins/forms/kexidatasourcepage.h index ac4342e7f..15570a86c 100644 --- a/src/plugins/forms/kexidatasourcepage.h +++ b/src/plugins/forms/kexidatasourcepage.h @@ -1,114 +1,120 @@ /* This file is part of the KDE project - Copyright (C) 2005-2009 Jarosław Staniek + Copyright (C) 2005-2017 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 KEXIDATASOURCEPAGE_H #define KEXIDATASOURCEPAGE_H #include "kexiformutils_export.h" #include #include #include #include #include #include class KexiDataSourceComboBox; class KexiFieldComboBox; class KexiFieldListView; class KexiProject; class QToolButton; class QLabel; //! A page within form designer's property tabbed pane, providing data source editor class KEXIFORMUTILS_EXPORT KexiDataSourcePage : public KexiPropertyPaneViewBase { Q_OBJECT public: explicit KexiDataSourcePage(QWidget *parent); virtual ~KexiDataSourcePage(); + //! @return name plugin ID of selected item (usually a table or a query). Can return an empty string. + QString selectedPluginId() const; + + //! @return name of selected table or query. + QString selectedName() const; + public Q_SLOTS: void setProject(KexiProject *prj); void clearFormDataSourceSelection(bool alsoClearComboBox = true); void clearWidgetDataSourceSelection(); //! Sets data source of a currently selected form. //! This is performed on form initialization and on activating. void setFormDataSource(const QString& pluginId, const QString& name); //! Receives a pointer to a new property \a set (from KexiFormView::managerPropertyChanged()) void assignPropertySet(KPropertySet* propertySet); Q_SIGNALS: //! Signal emitted when helper button 'go to selected data source' is clicked. void jumpToObjectRequested(const QString& mime, const QString& name); //! Signal emitted when form's data source has been changed. It's connected to the Form Manager. void formDataSourceChanged(const QString& mime, const QString& name); /*! Signal emitted when current widget's data source (field/expression) has been changed. It's connected to the Form Manager. \a caption for this field is also provided (e.g. AutoField form widget use it) */ void dataSourceFieldOrExpressionChanged(const QString& string, const QString& caption, KDbField::Type type); /*! Signal emitted when 'insert fields' button has been clicked */ void insertAutoFields(const QString& sourcePartClass, const QString& sourceName, const QStringList& fields); protected Q_SLOTS: void slotWidgetDataSourceTextChanged(const QString &text); void slotFormDataSourceTextChanged(const QString &text); void slotFormDataSourceChanged(); void slotFieldSelected(); void slotGotoSelected(); void slotInsertSelectedFields(); void slotFieldListViewSelectionChanged(); void slotFieldDoubleClicked(const QString& sourcePluginId, const QString& sourceName, const QString& fieldName); protected: void updateSourceFieldWidgetsAvailability(); KexiFieldComboBox *m_widgetDataSourceCombo; QWidget *m_widgetDataSourceComboSpacer; KexiDataSourceComboBox* m_formDataSourceCombo; QWidget *m_formDataSourceComboSpacer; QLabel *m_dataSourceLabel, *m_noDataSourceAvailableLabel, *m_widgetDSLabel; QToolButton *m_gotoButton; QString m_noDataSourceAvailableSingleText; QString m_noDataSourceAvailableMultiText; bool m_insideClearFormDataSourceSelection; #ifdef KEXI_AUTOFIELD_FORM_WIDGET_SUPPORT KexiFieldListView* m_availableFieldsLabel; KexiFieldListView* m_fieldListView; QLabel *m_mousePointerLabel; QLabel *m_availableFieldsDescriptionLabel; QToolButton *m_addField; #else KDbTableOrQuerySchema *m_tableOrQuerySchema; //!< temp. #endif //! Used only in assignPropertySet() to check whether we already have the set assigned QString m_currentObjectName; }; #endif diff --git a/src/plugins/forms/kexiformpart.cpp b/src/plugins/forms/kexiformpart.cpp index 9af5fb11d..9d349a969 100644 --- a/src/plugins/forms/kexiformpart.cpp +++ b/src/plugins/forms/kexiformpart.cpp @@ -1,389 +1,468 @@ /* This file is part of the KDE project Copyright (C) 2004 Lucijan Busch Copyright (C) 2004 Cedric Pasteur - Copyright (C) 2005 Jarosław Staniek + Copyright (C) 2005-2017 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 "kexiformpart.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "widgets/kexidbform.h" #include "kexiformscrollview.h" #include "kexiformmanager.h" #include "kexidatasourcepage.h" #include #include #include #include #include #include #include #include //! @todo #define KEXI_SHOW_SPLITTER_WIDGET //! @internal class Q_DECL_HIDDEN KexiFormPart::Private { public: Private() { } ~Private() { delete static_cast(widgetTreeWidget); delete static_cast(dataSourcePage); } QPointer dataSourcePage; QPointer widgetTree; QPointer widgetTreeWidget; }; KexiFormPart::KexiFormPart(QObject *parent, const QVariantList &l) : KexiPart::Part(parent, xi18nc("Translate this word using only lowercase alphanumeric characters (a..z, 0..9). " "Use '_' character instead of spaces. First character should be a..z character. " "If you cannot use latin characters in your language, use english word.", "form"), xi18nc("tooltip", "Create new form"), xi18nc("what's this", "Creates new form."), l) , d(new Private) { setInternalPropertyValue("newObjectsAreDirty", true); // Only create form manager if it's not yet created. // KexiReportPart could have created it already. KexiFormManager::self()->init(this, d->widgetTree); // this should create KexiFormManager singleton } KexiFormPart::~KexiFormPart() { delete d; } void KexiFormPart::initPartActions() { } void KexiFormPart::initInstanceActions() { //connect actions provided by widget factories createSharedAction(Kexi::DesignViewMode, xi18n("Clear Widget Contents"), koIconName("edit-clear"), QKeySequence(), "formpart_clear_contents"); createSharedAction(Kexi::DesignViewMode, xi18n("Edit Tab Order..."), KexiIconName("widgets-tab-order"), QKeySequence(), "formpart_taborder"); //! @todo createSharedAction(Kexi::DesignViewMode, xi18n("Edit Pixmap Collection"), koIconName("icons"), 0, "formpart_pixmap_collection"); //! @todo createSharedAction(Kexi::DesignViewMode, xi18n("Edit Form Connections"), koIconName("connections"), 0, "formpart_connections"); createSharedAction(Kexi::DesignViewMode, xi18n("Bring Widget to Front"), koIconName("object-order-front"), QKeySequence(), "formpart_format_raise"); createSharedAction(Kexi::DesignViewMode, xi18n("Send Widget to Back"), koIconName("object-order-back"), QKeySequence(), "formpart_format_lower"); #ifdef KEXI_SHOW_UNFINISHED createSharedAction(Kexi::DesignViewMode, futureI18n("Other Widgets"), QString(), QKeySequence(), "other_widgets_menu", "KActionMenu"); #endif QAction *action = createSharedAction(Kexi::DesignViewMode, xi18n("Align Widgets Position"), koIconName("align-horizontal-left"), QKeySequence(), "formpart_align_menu", "KActionMenu"); KActionMenu *menu = static_cast(action); menu->addAction(createSharedAction(Kexi::DesignViewMode, xi18n("To Left"), koIconName("align-horizontal-left"), QKeySequence(), "formpart_align_to_left")); menu->addAction(createSharedAction(Kexi::DesignViewMode, xi18n("To Right"), koIconName("align-horizontal-right"), QKeySequence(), "formpart_align_to_right")); menu->addAction(createSharedAction(Kexi::DesignViewMode, xi18n("To Top"), koIconName("align-vertical-top"), QKeySequence(), "formpart_align_to_top")); menu->addAction(createSharedAction(Kexi::DesignViewMode, xi18n("To Bottom"), koIconName("align-vertical-bottom"), QKeySequence(), "formpart_align_to_bottom")); menu->addAction(createSharedAction(Kexi::DesignViewMode, xi18n("To Grid"), koIconName("align-grid"), QKeySequence(), "formpart_align_to_grid")); action = createSharedAction(Kexi::DesignViewMode, xi18n("Adjust Widgets Size"), koIconName("fit-grid"), QKeySequence(), "formpart_adjust_size_menu", "KActionMenu"); menu = static_cast(action); menu->addAction(createSharedAction(Kexi::DesignViewMode, xi18n("To Fit"), koIconName("fit-contents"), QKeySequence(), "formpart_adjust_to_fit")); menu->addAction(createSharedAction(Kexi::DesignViewMode, xi18n("To Grid"), koIconName("fit-grid"), QKeySequence(), "formpart_adjust_size_grid")); menu->addAction(createSharedAction(Kexi::DesignViewMode, xi18n("To Shortest"), koIconName("fit-shortest"), QKeySequence(), "formpart_adjust_height_small")); menu->addAction(createSharedAction(Kexi::DesignViewMode, xi18n("To Tallest"), koIconName("fit-tallest"), QKeySequence(), "formpart_adjust_height_big")); menu->addAction(createSharedAction(Kexi::DesignViewMode, xi18n("To Narrowest"), koIconName("fit-narrowest"), QKeySequence(), "formpart_adjust_width_small")); menu->addAction(createSharedAction(Kexi::DesignViewMode, xi18n("To Widest"), koIconName("fit-widest"), QKeySequence(), "formpart_adjust_width_big")); } KexiWindowData* KexiFormPart::createWindowData(KexiWindow* window) { - return new KexiFormPartTempData(window); + KexiMainWindowIface *win = KexiMainWindowIface::global(); + return new KexiFormPartTempData(window, win->project()->dbConnection()); } KexiView* KexiFormPart::createView(QWidget *parent, KexiWindow* window, KexiPart::Item *item, Kexi::ViewMode viewMode, QMap*) { Q_ASSERT(item); Q_UNUSED(window); Q_UNUSED(viewMode); KexiMainWindowIface *win = KexiMainWindowIface::global(); if (!win || !win->project() || !win->project()->dbConnection()) return 0; KexiFormView *view = new KexiFormView(parent, win->project()->dbConnection()); view->setObjectName(item->name().toLatin1()); return view; } #ifndef KEXI_NO_FORM_DATASOURCE_WIZARD void KexiFormPart::generateForm(KDbFieldList *list, QDomDocument &domDoc) { //this form generates a .ui from KDbFieldList list //basically that is a Label and a LineEdit for each field domDoc = QDomDocument("UI"); QDomElement uiElement = domDoc.createElement("UI"); domDoc.appendChild(uiElement); uiElement.setAttribute("version", "3.1"); uiElement.setAttribute("stdsetdef", 1); QDomElement baseClass = domDoc.createElement("class"); uiElement.appendChild(baseClass); QDomText baseClassV = domDoc.createTextNode("QWidget"); baseClass.appendChild(baseClassV); QDomElement baseWidget = domDoc.createElement("widget"); baseWidget.setAttribute("class", "QWidget"); int y = 0; for (unsigned int i = 0; i < list->fieldCount(); i++) { QDomElement lclass = domDoc.createElement("widget"); baseWidget.appendChild(lclass); lclass.setAttribute("class", "QLabel"); QDomElement lNameProperty = domDoc.createElement("property"); lNameProperty.setAttribute("name", "name"); QDomElement lType = domDoc.createElement("cstring"); QDomText lClassN = domDoc.createTextNode(QString("l%1").arg(list->field(i)->name())); lType.appendChild(lClassN); lNameProperty.appendChild(lType); lclass.appendChild(lNameProperty); QDomElement gNameProperty = domDoc.createElement("property"); gNameProperty.setAttribute("name", "geometry"); QDomElement lGType = domDoc.createElement("rect"); QDomElement lx = domDoc.createElement("x"); QDomText lxV = domDoc.createTextNode("10"); lx.appendChild(lxV); QDomElement ly = domDoc.createElement("y"); QDomText lyV = domDoc.createTextNode(QString::number(y + 10)); ly.appendChild(lyV); QDomElement lWidth = domDoc.createElement("width"); QDomText lWidthV = domDoc.createTextNode("100"); lWidth.appendChild(lWidthV); QDomElement lHeight = domDoc.createElement("height"); QDomText lHeightV = domDoc.createTextNode("20"); lHeight.appendChild(lHeightV); lGType.appendChild(lx); lGType.appendChild(ly); lGType.appendChild(lWidth); lGType.appendChild(lHeight); gNameProperty.appendChild(lGType); lclass.appendChild(gNameProperty); QDomElement tNameProperty = domDoc.createElement("property"); tNameProperty.setAttribute("name", "text"); QDomElement lTType = domDoc.createElement("string"); QDomText lTextV = domDoc.createTextNode(list->field(i)->name()); lTType.appendChild(lTextV); tNameProperty.appendChild(lTType); lclass.appendChild(tNameProperty); ///line edit! QDomElement vclass = domDoc.createElement("widget"); baseWidget.appendChild(vclass); vclass.setAttribute("class", "QLineEdit"); QDomElement vNameProperty = domDoc.createElement("property"); vNameProperty.setAttribute("name", "name"); QDomElement vType = domDoc.createElement("cstring"); QDomText vClassN = domDoc.createTextNode(list->field(i)->name()); vType.appendChild(vClassN); vNameProperty.appendChild(vType); vclass.appendChild(vNameProperty); QDomElement vgNameProperty = domDoc.createElement("property"); vgNameProperty.setAttribute("name", "geometry"); QDomElement vGType = domDoc.createElement("rect"); QDomElement vx = domDoc.createElement("x"); QDomText vxV = domDoc.createTextNode("110"); vx.appendChild(vxV); QDomElement vy = domDoc.createElement("y"); QDomText vyV = domDoc.createTextNode(QString::number(y + 10)); vy.appendChild(vyV); QDomElement vWidth = domDoc.createElement("width"); QDomText vWidthV = domDoc.createTextNode("200"); vWidth.appendChild(vWidthV); QDomElement vHeight = domDoc.createElement("height"); QDomText vHeightV = domDoc.createTextNode("20"); vHeight.appendChild(vHeightV); vGType.appendChild(vx); vGType.appendChild(vy); vGType.appendChild(vWidth); vGType.appendChild(vHeight); vgNameProperty.appendChild(vGType); vclass.appendChild(vgNameProperty); y += 20; } QDomElement lNameProperty = domDoc.createElement("property"); lNameProperty.setAttribute("name", "name"); QDomElement lType = domDoc.createElement("cstring"); QDomText lClassN = domDoc.createTextNode("DBForm"); lType.appendChild(lClassN); lNameProperty.appendChild(lType); baseWidget.appendChild(lNameProperty); QDomElement wNameProperty = domDoc.createElement("property"); wNameProperty.setAttribute("name", "geometry"); QDomElement wGType = domDoc.createElement("rect"); QDomElement wx = domDoc.createElement("x"); QDomText wxV = domDoc.createTextNode("0"); wx.appendChild(wxV); QDomElement wy = domDoc.createElement("y"); QDomText wyV = domDoc.createTextNode("0"); wy.appendChild(wyV); QDomElement wWidth = domDoc.createElement("width"); QDomText wWidthV = domDoc.createTextNode("340"); wWidth.appendChild(wWidthV); QDomElement wHeight = domDoc.createElement("height"); QDomText wHeightV = domDoc.createTextNode(QString::number(y + 30)); wHeight.appendChild(wHeightV); wGType.appendChild(wx); wGType.appendChild(wy); wGType.appendChild(wWidth); wGType.appendChild(wHeight); wNameProperty.appendChild(wGType); baseWidget.appendChild(wNameProperty); uiElement.appendChild(baseWidget); } #endif KLocalizedString KexiFormPart::i18nMessage( const QString& englishMessage, KexiWindow* window) const { Q_UNUSED(window); if (englishMessage == "Design of object %1 has been modified.") return kxi18nc(I18NC_NOOP("@info", "Design of form %1 has been modified.")); if (englishMessage == "Object %1 already exists.") return kxi18nc(I18NC_NOOP("@info", "Form %1 already exists.")); return Part::i18nMessage(englishMessage, window); } KexiDataSourcePage* KexiFormPart::dataSourcePage() const { return d->dataSourcePage; } KFormDesigner::WidgetTreeWidget* KexiFormPart::widgetTreePage() const { return d->widgetTree; } void KexiFormPart::setupCustomPropertyPanelTabs(QTabWidget *tab) { if (!d->dataSourcePage) { d->dataSourcePage = new KexiDataSourcePage(0); d->dataSourcePage->setObjectName("dataSourcePage"); connect(d->dataSourcePage, SIGNAL(jumpToObjectRequested(QString,QString)), KexiMainWindowIface::global()->thisWidget(), SLOT(highlightObject(QString,QString))); connect(d->dataSourcePage, SIGNAL(formDataSourceChanged(QString,QString)), KexiFormManager::self(), SLOT(setFormDataSource(QString,QString))); connect(d->dataSourcePage, SIGNAL(dataSourceFieldOrExpressionChanged(QString,QString,KDbField::Type)), KexiFormManager::self(), SLOT(setDataSourceFieldOrExpression(QString,QString,KDbField::Type))); #ifdef KEXI_AUTOFIELD_FORM_WIDGET_SUPPORT connect(d->dataSourcePage, SIGNAL(insertAutoFields(QString,QString,QStringList)), KexiFormManager::self(), SLOT(insertAutoFields(QString,QString,QStringList))); #endif } KexiProject *prj = KexiMainWindowIface::global()->project(); d->dataSourcePage->setProject(prj); tab->addTab(d->dataSourcePage, koIcon("server-database"), QString()); tab->setTabToolTip(tab->indexOf(d->dataSourcePage), xi18n("Data Source")); if (!d->widgetTreeWidget) { d->widgetTreeWidget = new QWidget; QVBoxLayout *lyr = new QVBoxLayout(d->widgetTreeWidget); lyr->setContentsMargins(2, 2, 2, 2); d->widgetTree = new KFormDesigner::WidgetTreeWidget; d->widgetTree->setObjectName("KexiFormPart:WidgetTreeWidget"); lyr->addWidget(d->widgetTree); } tab->addTab(d->widgetTreeWidget, KexiIcon("widgets"), QString()); tab->setTabToolTip(tab->indexOf(d->widgetTreeWidget), xi18n("Widgets")); } //---------------- -KexiFormPartTempData::KexiFormPartTempData(KexiWindow* parent) - : KexiWindowData(parent) +class Q_DECL_HIDDEN KexiFormPartTempData::Private { +public: + Private(KexiFormPartTempData *temp) + : q(temp) + { + } + + void unregisterForChanges() + { + if (dataSource.isEmpty()) { + return; + } + if (pluginId == "org.kexi-project.table") { + KDbTableSchema *table = conn->tableSchema(dataSource); + if (!table) { + return; + } + KDbTableSchemaChangeListener::unregisterForChanges(conn, table); + } else if (pluginId == "org.kexi-project.query") { + KDbQuerySchema *query = conn->querySchema(dataSource); + if (!query) { + return; + } + KDbTableSchemaChangeListener::unregisterForChanges(conn, query); + } else { + return; + } + } + + void registerForChanges(const QString &newPluginId, const QString &newDataSource) + { + if (newPluginId == "org.kexi-project.table") { + KDbTableSchema *table = conn->tableSchema(newDataSource); + if (!table) { + return; + } + KDbTableSchemaChangeListener::registerForChanges(conn, q, table); + } else if (pluginId == "org.kexi-project.query") { + KDbQuerySchema *query = conn->querySchema(newDataSource); + if (!query) { + return; + } + KDbTableSchemaChangeListener::registerForChanges(conn, q, query); + } else { + return; + } + pluginId = newPluginId; + dataSource = newDataSource; + } + + KDbConnection *conn; + QString pluginId; + QString dataSource; + +private: + KexiFormPartTempData * const q; +}; + +KexiFormPartTempData::KexiFormPartTempData(KexiWindow* parent, KDbConnection *conn) + : KexiWindowData(parent), d(new Private(this)) +{ + d->conn = conn; + setName(KexiUtils::localizedStringToHtmlSubstring( + kxi18nc("@info", "Form %1").subs(parent->partItem()->name()))); } KexiFormPartTempData::~KexiFormPartTempData() { + KDbTableSchemaChangeListener::unregisterForChanges(d->conn, this); + delete d; } +void KexiFormPartTempData::setDataSource(const QString &pluginId, const QString &dataSource) +{ + if (d->pluginId != pluginId || d->dataSource != dataSource) { + d->unregisterForChanges(); + d->registerForChanges(pluginId, dataSource); + } +} + +tristate KexiFormPartTempData::closeListener() +{ + KexiWindow* window = static_cast(parent()); + qDebug() << window->partItem()->name(); + return KexiMainWindowIface::global()->closeWindow(window); +} diff --git a/src/plugins/forms/kexiformpart.h b/src/plugins/forms/kexiformpart.h index fe1241d86..d18c3053f 100644 --- a/src/plugins/forms/kexiformpart.h +++ b/src/plugins/forms/kexiformpart.h @@ -1,95 +1,113 @@ /* This file is part of the KDE project Copyright (C) 2004 Lucijan Busch Copyright (C) 2004 Cedric Pasteur - Copyright (C) 2005-2009 Jarosław Staniek + Copyright (C) 2005-2017 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 KEXIFORMPART_H #define KEXIFORMPART_H #include #include #include #include #include +#include + class QDomDocument; namespace KFormDesigner { class Form; class WidgetTreeWidget; } class KDbFieldList; class KexiDataSourcePage; -class KexiFormPartTempData : public KexiWindowData +class KexiFormPartTempData : public KexiWindowData, public KDbTableSchemaChangeListener { Q_OBJECT public: - explicit KexiFormPartTempData(KexiWindow* parent); + KexiFormPartTempData(KexiWindow* parent, KDbConnection *conn); ~KexiFormPartTempData(); + + //! Sets data source used for this data. + //! If the previous data source is different and is not empty, listener for it will be unregistered. + //! If the new data source is empty this temp-data object will be registered as a listener for it. + void setDataSource(const QString &pluginId, const QString &dataSource); + QPointer form; QPointer previewForm; QString tempForm; QPoint scrollViewContentsPos; //!< to preserve contents pos after switching //! Used in KexiFormView::setUnsavedLocalBLOBs() QHash unsavedLocalBLOBs; //! Used when loading a form from (temporary) XML in Data View //! to get unsaved blobs collected at design mode. QHash unsavedLocalBLOBsByName; + +protected: + //! This temp-data acts as a listener for tracking changes in table schema + //! used by the form. This method closes the form on request. + tristate closeListener() override; + +private: + Q_DISABLE_COPY(KexiFormPartTempData) + class Private; + Private * const d; }; //! Kexi Form Plugin /*! It just creates a \ref KexiFormView. See there for most of code. */ class KEXIFORMUTILS_EXPORT KexiFormPart : public KexiPart::Part { Q_OBJECT public: KexiFormPart(QObject *parent, const QVariantList &); virtual ~KexiFormPart(); KexiDataSourcePage* dataSourcePage() const; KFormDesigner::WidgetTreeWidget* widgetTreePage() const; #ifndef KEXI_NO_FORM_DATASOURCE_WIZARD void generateForm(KDbFieldList *list, QDomDocument &domDoc); #endif virtual KLocalizedString i18nMessage(const QString& englishMessage, KexiWindow* window) const; protected: KexiWindowData* createWindowData(KexiWindow* window) override Q_REQUIRED_RESULT; KexiView *createView(QWidget *parent, KexiWindow *window, KexiPart::Item *item, Kexi::ViewMode viewMode = Kexi::DataViewMode, QMap *staticObjectArgs = nullptr) override Q_REQUIRED_RESULT; virtual void initPartActions(); virtual void initInstanceActions(); virtual void setupCustomPropertyPanelTabs(QTabWidget *tab); private: class Private; Private* d; }; #endif diff --git a/src/plugins/forms/kexiformview.cpp b/src/plugins/forms/kexiformview.cpp index 3c10b5eab..02fce9cbe 100644 --- a/src/plugins/forms/kexiformview.cpp +++ b/src/plugins/forms/kexiformview.cpp @@ -1,1300 +1,1308 @@ /* This file is part of the KDE project Copyright (C) 2004 Cedric Pasteur - Copyright (C) 2004-2016 Jarosław Staniek + Copyright (C) 2004-2017 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 "widgets/kexidbform.h" #include "kexiformscrollview.h" #include "kexidatasourcepage.h" #include "kexiformmanager.h" #ifdef KEXI_AUTOFIELD_FORM_WIDGET_SUPPORT #include "kexidbautofield.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //! @todo #define KEXI_SHOW_SPLITTER_WIDGET class Q_DECL_HIDDEN 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; KDbQuerySchema* 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; KDbCursor *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_AUTOFIELD_FORM_WIDGET_SUPPORT /*! 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() (void)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() { deleteQuery(); propertySetSwitched(); delete d; } void KexiFormView::deleteQuery() { if (d->cursor) { KDbConnection *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; } bool 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( xi18nc("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; KDbFieldList *fields = 0; #ifndef KEXI_NO_FORM_DATASOURCE_WIZARD 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 KEXI_NO_FORM_DATASOURCE_WIZARD QDomDocument dom; formPart()->generateForm(fields, dom); KFormDesigner::FormIO::loadFormFromDom(form(), d->dbform, &dom); //! @todo handle errors #endif } else { if (!loadForm()) { return false; } } 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(); } return true; } void KexiFormView::updateAutoFieldsDataSource() { #ifdef KEXI_AUTOFIELD_FORM_WIDGET_SUPPORT //! @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->dataSourcePluginId()); KDbConnection *conn = KexiMainWindowIface::global()->project()->dbConnection(); KDbTableOrQuerySchema tableOrQuery( conn, dataSourceString.toLatin1(), dataSourcePartClassString == "org.kexi-project.table" ? KDbTableOrQuerySchema::Type::Table :: KDbTableOrQuerySchema::Type::Query); if (!tableOrQuery.table() && !tableOrQuery.query()) return; foreach (KFormDesigner::ObjectTreeItem *item, *form()->objectTree()->hash()) { KexiDBAutoField *afWidget = dynamic_cast(item->widget()); if (afWidget) { KDbQueryColumnInfo *colInfo = tableOrQuery.columnInfo(afWidget->dataSource()); if (colInfo) { afWidget->setColumnInfo(colInfo); } } } #endif } 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->dataSourcePluginId()); KDbConnection *conn = KexiMainWindowIface::global()->project()->dbConnection(); KDbTableOrQuerySchema tableOrQuery(conn, dataSourceString.toLatin1(), dataSourcePartClassString == "org.kexi-project.table" ? KDbTableOrQuerySchema::Type::Table : KDbTableOrQuerySchema::Type::Query); 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) { //qDebug() << "delayed setting of the subproperty: widget=" // << item->widget()->objectName() << " prop=" << subpropIt.key() << " val=" // << subpropIt.value(); QMetaProperty meta = KexiUtils::findPropertyWithSuperclasses( subwidget, qPrintable(subpropIt.key())); 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", int(blobID)); } const QList list(widget->findChildren()); if (list.isEmpty()) return; foreach(QWidget *w, list) { setUnsavedBLOBIdsForDataViewMode(w, unsavedLocalBLOBsByName); } } } bool KexiFormView::loadForm() { //! @todo also load d->resizeMode //qDebug() << "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()) { if (!KFormDesigner::FormIO::loadFormFromString(form(), d->dbform, tempData()->tempForm)) { return false; } setUnsavedBLOBIdsForDataViewMode(d->dbform, tempData()->unsavedLocalBLOBsByName); updateAutoFieldsDataSource(); updateValuesForSubproperties(); return true; } if (!window()->neverSaved()) { // normal load QString data; if (!loadDataBlock(&data)) { return false; } if (!KFormDesigner::FormIO::loadFormFromString(form(), d->dbform, data)) { return false; } + tempData()->setDataSource(d->dbform->dataSourcePluginId(), d->dbform->dataSource()); } //"autoTabStops" property is loaded -set it within the form tree as well form()->setAutoTabStops(d->dbform->autoTabStops()); updateAutoFieldsDataSource(); updateValuesForSubproperties(); return true; } void KexiFormView::slotPropertySetSwitched() { propertySetReloaded(); if (viewMode() == Kexi::DesignViewMode) { formPart()->dataSourcePage()->assignPropertySet(form()->propertySet()); } } tristate KexiFormView::beforeSwitchTo(Kexi::ViewMode mode, bool *dontStore) { Q_ASSERT(dontStore); if (mode != viewMode()) { if (viewMode() == Kexi::DataViewMode) { if (!d->scrollView->acceptRecordEditing()) return cancelled; d->scrollView->beforeSwitchView(); } else { //remember our pos tempData()->scrollViewContentsPos = QPoint(d->scrollView->horizontalScrollBar()->value(), d->scrollView->verticalScrollBar()->value()); } } if (d->scrollView->data() && viewMode() == Kexi::DataViewMode) { //old data won't be needed nor valid d->scrollView->setData(0, false); } // 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 == Kexi::DataViewMode) { //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() if (!initForm()) { return false; } //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(); //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) { //qDebug() << 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->selectFirstRecord(); } //dirty only if it's a new object if (mode == Kexi::NoViewMode) setDirty(window()->partItem()->neverSaved()); updateActionsInternal(); return true; } KPropertySet* 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() { deleteQuery(); //! @todo also handle anonymous (not stored) queries provided as statements here KDbTableSchema *tableSchema = 0; KDbConnection *conn = 0; QStringList sources; bool forceReadOnlyDataSource = false; QString dataSourceString(d->dbform->dataSource()); bool ok = !dataSourceString.isEmpty(); 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->dataSourcePluginId()); 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 KDbQuerySchema(); d->queryIsOwned = true; if (dataSourcePartClassString.isEmpty()) d->dbform->setDataSourcePluginId("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->setDataSourcePluginId("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 KDb. forceReadOnlyDataSource = true; } else { //no other classes are supported ok = false; } } } QSet invalidSources; if (ok) { KDbIndexSchema *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(); //qDebug() << "pkey added to data sources:" << pkey->names(); } //qDebug() << "sources=" << sources; int 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); KDbField *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); //qDebug() << "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 { qDebug() << d->query->parameters(conn); // like in KexiQueryView::executeQuery() QList params; { KexiUtils::WaitCursorRemover remover; params = KexiQueryParameters::getParameters(this, conn, d->query, &ok); } if (ok) //input cancelled d->cursor = conn->executeQuery(d->query, params); } d->scrollView->invalidateDataSources( invalidSources, d->cursor ? d->cursor->connection() : nullptr, d->query); ok = d->cursor != 0; } if (!invalidSources.isEmpty()) d->dbform->updateTabStopsOrder(); if (ok) { //! @todo PRIMITIVE!! data setting: //! @todo KDbTableViewData is not a great name for data class here... rename/move? KDbTableViewData* data = new KDbTableViewData(d->cursor); if (forceReadOnlyDataSource) data->setReadOnly(true); data->preloadAllRecords(); ///*! @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*/); } else { d->scrollView->setData(0, false); } } void KexiFormView::setFormModified() { form()->setModified(true); } KDbObject* KexiFormView::storeNewData(const KDbObject& object, KexiView::StoreNewDataOptions options, bool *cancel) { Q_ASSERT(cancel); KDbObject *s = KexiView::storeNewData(object, options, cancel); //qDebug() << "new id:" << s->id(); if (!s || *cancel) { delete s; return 0; } if (!storeData()) { //failure: remove object's object data to avoid garbage KDbConnection *conn = KexiMainWindowIface::global()->project()->dbConnection(); conn->removeObject(s->id()); delete s; return 0; } return s; } tristate KexiFormView::storeData(bool dontAsk) { Q_UNUSED(dontAsk); //qDebug() << window()->partItem()->name() << "[" << window()->id() << "]"; //-- first, store local BLOBs, so identifiers can be updated //! @todo remove unused data stored previously KDbConnection *conn = KexiMainWindowIface::global()->project()->dbConnection(); KDbTableSchema *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(); KDbFieldList *blobsFieldsWithoutID = blobsTable->subList(blobsFieldNamesWithoutID); KDbPreparedStatement st = conn->prepareStatement( KDbPreparedStatement::InsertStatement, blobsFieldsWithoutID); if (!st.isValid()) { 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()) { qWarning() << "it.key()==0 !"; continue; } //qDebug() << "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()); KDbPreparedStatementParameters parameters; parameters << h.data() << originalFileName << caption << h.mimeType() << int(/*! @todo unsafe */h.folderId()); if (!st.execute(parameters)) { delete blobsFieldsWithoutID; qWarning() << "execute error"; return false; } delete blobsFieldsWithoutID; blobsFieldsWithoutID = 0; const quint64 storedBLOBID = KDb::lastInsertedAutoIncValue( conn, st.lastInsertRecordId(), "o_id", "kexi__blobs"); if (std::numeric_limits::max() == storedBLOBID) { //! @todo show message? return false; } //qDebug() << "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(int(storedBLOBID))); KFormDesigner::ObjectTreeItem *widgetItem = designFormView->form()->objectTree()->lookup(it.key()->objectName()); if (widgetItem) widgetItem->addModifiedProperty("storedPixmapId", oldStoredPixmapId); else qWarning() << "no" << it.key()->objectName() << "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); //! @todo KEXI3 Port this.. //! @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) { // qDebug() << 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) { KPropertySet *set = form()->propertySet(); - const QString dataSourcePartClass = set->propertyValue("dataSourcePartClass").toString(); + QString dataSourcePartClass = set->propertyValue("dataSourcePartClass").toString(); const QString dataSource = set->propertyValue("dataSource").toString(); formPart()->dataSourcePage()->setFormDataSource(dataSourcePartClass, dataSource); + if (dataSourcePartClass.isEmpty() + && !formPart()->dataSourcePage()->selectedPluginId().isEmpty()) + { + set->property("dataSourcePartClass") + .setValue(formPart()->dataSourcePage()->selectedPluginId(), + KProperty::ValueOption::IgnoreOld); + } } } void KexiFormView::slotHandleDragMoveEvent(QDragMoveEvent* e) { #ifdef KEXI_AUTOFIELD_FORM_WIDGET_SUPPORT if (KexiFieldDrag::canDecode(e)) { e->setAccepted(true); } #else Q_UNUSED(e); #endif } void KexiFormView::slotHandleDropEvent(QDropEvent* e) { #ifdef KEXI_AUTOFIELD_FORM_WIDGET_SUPPORT 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()); } #else Q_UNUSED(e); #endif } void KexiFormView::insertAutoFields(const QString& sourcePartClass, const QString& sourceName, const QStringList& fields, KFormDesigner::Container* targetContainer, const QPoint& _pos) { #ifdef KEXI_AUTOFIELD_FORM_WIDGET_SUPPORT if (fields.isEmpty()) return; KDbConnection *conn = KexiMainWindowIface::global()->project()->dbConnection(); KDbTableOrQuerySchema tableOrQuery(conn, sourceName.toLatin1(), sourcePartClass == "org.kexi-project.table" ? KDbTableOrQuerySchema::Type::Table : KDbTableOrQuerySchema::Type::Query); if (!tableOrQuery.table() && !tableOrQuery.query()) { qWarning() << "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) { KDbQueryColumnInfo* column = tableOrQuery.columnInfo(field); if (!column) { qWarning() << "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); //qDebug() << *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 #else Q_UNUSED(sourcePartClass); Q_UNUSED(sourceName); Q_UNUSED(fields); Q_UNUSED(targetContainer); Q_UNUSED(_pos); #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); //qDebug() << 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(); //qDebug() << selectedWidget << (viewMode()==Kexi::DesignViewMode) << widget_assign_action; QByteArray wClass; if (selectedWidget) { wClass = selectedWidget->metaObject()->className(); //qDebug() << 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 } diff --git a/src/plugins/forms/kexiformview.h b/src/plugins/forms/kexiformview.h index dac403185..b94bd46ab 100644 --- a/src/plugins/forms/kexiformview.h +++ b/src/plugins/forms/kexiformview.h @@ -1,175 +1,175 @@ /* This file is part of the KDE project Copyright (C) 2004 Cedric Pasteur - Copyright (C) 2004-2016 Jarosław Staniek + Copyright (C) 2004-2017 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 "kexiformutils_export.h" #define KEXI_NO_FORM_DATASOURCE_WIZARD class KexiFormPart; class KexiFormPartTempData; 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 KDb-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 KPropertySet* propertySet(); virtual KDbObject* storeNewData(const KDbObject& object, KexiView::StoreNewDataOptions options, bool *cancel); virtual tristate storeData(bool dontAsk = false); KexiFormPartTempData* tempData() const; KexiFormPart* formPart() const; void setForm(KFormDesigner::Form *f); bool initForm(); bool 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(); 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/src/plugins/queries/kexiquerypart.cpp b/src/plugins/queries/kexiquerypart.cpp index 00f241a87..1bbef6115 100644 --- a/src/plugins/queries/kexiquerypart.cpp +++ b/src/plugins/queries/kexiquerypart.cpp @@ -1,269 +1,339 @@ /* This file is part of the KDE project Copyright (C) 2004 Lucijan Busch - Copyright (C) 2004-2016 Jarosław Staniek + Copyright (C) 2004-2017 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 "kexiquerypart.h" #include "kexiqueryview.h" #include "kexiquerydesignerguieditor.h" #include "kexiquerydesignersql.h" #include #include #include #include #include #include #include #include #include +#include +#include + #include KEXI_PLUGIN_FACTORY(KexiQueryPart, "kexi_queryplugin.json") KexiQueryPart::KexiQueryPart(QObject *parent, const QVariantList &l) : KexiPart::Part(parent, xi18nc("Translate this word using only lowercase alphanumeric characters (a..z, 0..9). " "Use '_' character instead of spaces. First character should be a..z character. " "If you cannot use latin characters in your language, use english word.", "query"), xi18nc("tooltip", "Create new query"), xi18nc("what's this", "Creates new query."), l) { setInternalPropertyValue("textViewModeCaption", xi18n("SQL")); } KexiQueryPart::~KexiQueryPart() { } KexiWindowData* KexiQueryPart::createWindowData(KexiWindow* window) { return new KexiQueryPartTempData(window, KexiMainWindowIface::global()->project()->dbConnection()); } KexiView* KexiQueryPart::createView(QWidget *parent, KexiWindow* window, KexiPart::Item *item, Kexi::ViewMode viewMode, QMap*) { Q_ASSERT(item); Q_UNUSED(item); Q_UNUSED(window); //qDebug(); KexiView* view = 0; if (viewMode == Kexi::DataViewMode) { view = new KexiQueryView(parent); view->setObjectName("dataview"); } else if (viewMode == Kexi::DesignViewMode) { view = new KexiQueryDesignerGuiEditor(parent); view->setObjectName("guieditor"); //needed for updating tables combo box: KexiProject *prj = KexiMainWindowIface::global()->project(); connect(prj, SIGNAL(newItemStored(KexiPart::Item*)), view, SLOT(slotNewItemStored(KexiPart::Item*))); connect(prj, SIGNAL(itemRemoved(KexiPart::Item)), view, SLOT(slotItemRemoved(KexiPart::Item))); connect(prj, SIGNAL(itemRenamed(KexiPart::Item,QString)), view, SLOT(slotItemRenamed(KexiPart::Item,QString))); } else if (viewMode == Kexi::TextViewMode) { view = new KexiQueryDesignerSqlView(parent); view->setObjectName("sqldesigner"); } return view; } tristate KexiQueryPart::remove(KexiPart::Item *item) { if (!KexiMainWindowIface::global()->project() || !KexiMainWindowIface::global()->project()->dbConnection()) return false; KDbConnection *conn = KexiMainWindowIface::global()->project()->dbConnection(); KDbQuerySchema *sch = conn->querySchema(item->identifier()); - if (sch) + if (sch) { + const tristate res = KexiQueryPart::askForClosingObjectsUsingQuerySchema( + KexiMainWindowIface::global()->openedWindowFor(item->identifier()), conn, sch, + kxi18n("You are about to delete query %1 but it is used by " + "following opened windows:") + .subs(sch->name())); + if (res != true) { + return res; + } return conn->dropQuery(sch); + } //last chance: just remove item return conn->removeObject(item->identifier()); } void KexiQueryPart::initPartActions() { } void KexiQueryPart::initInstanceActions() { } KDbObject* KexiQueryPart::loadSchemaObject( KexiWindow *window, const KDbObject& object, Kexi::ViewMode viewMode, bool *ownedByWindow) { KexiQueryPartTempData * temp = static_cast(window->data()); QString sql; if (!loadDataBlock(window, &sql, "sql")) { return 0; } KDbEscapedString sqlText(sql); KDbParser *parser = KexiMainWindowIface::global()->project()->sqlParser(); KDbQuerySchema *query = 0; if (parser->parse(sqlText)) { query = parser->query(); } //error? if (!query) { if (viewMode == Kexi::TextViewMode) { //for SQL view, no parsing is initially needed: //-just make a copy: return KexiPart::Part::loadSchemaObject(window, object, viewMode, ownedByWindow); } /* Set this to true on data loading loadSchemaObject() to indicate that TextView mode could be used instead of DataView or DesignView, because there are problems with opening object. */ temp->proposeOpeningInTextViewModeBecauseOfProblems = true; //! @todo return 0; } qDebug() << KDbConnectionAndQuerySchema( KexiMainWindowIface::global()->project()->dbConnection(), *query); (KDbObject&)*query = object; //copy main attributes temp->registerTableSchemaChanges(query); if (ownedByWindow) *ownedByWindow = false; qDebug() << KDbConnectionAndQuerySchema( KexiMainWindowIface::global()->project()->dbConnection(), *query); return query; } KDbQuerySchema *KexiQueryPart::currentQuery(KexiView* view) { if (!view) return 0; KexiQueryView *qvp = 0; if (!(qvp = qobject_cast(view))) { return 0; } return static_cast(qvp->window()->data())->query(); } KLocalizedString KexiQueryPart::i18nMessage(const QString& englishMessage, KexiWindow* window) const { if (englishMessage == "Design of object %1 has been modified.") return kxi18nc(I18NC_NOOP("@info", "Design of query %1 has been modified.")); if (englishMessage == "Object %1 already exists.") return kxi18nc(I18NC_NOOP("@info", "Query %1 already exists.")); return Part::i18nMessage(englishMessage, window); } tristate KexiQueryPart::rename(KexiPart::Item *item, const QString& newName) { Q_ASSERT(item); Q_UNUSED(newName); if (!KexiMainWindowIface::global()->project()->dbConnection()) return false; + // Note: unlike in KexiTablePart::rename here we just mark the query obsolete here so no need + // to call KexiQueryPart::askForClosingObjectsUsingQuerySchema(). KexiMainWindowIface::global()->project()->dbConnection() ->setQuerySchemaObsolete(item->name()); return true; } +// static +tristate KexiQueryPart::askForClosingObjectsUsingQuerySchema(KexiWindow *window, + KDbConnection *conn, + KDbQuerySchema *query, + const KLocalizedString &msg) +{ + Q_ASSERT(conn); + Q_ASSERT(query); + if (!window) { + return true; + } + QList listeners + = KDbTableSchemaChangeListener::listeners(conn, query); + KexiQueryPartTempData *temp = static_cast(window->data()); + // Special case: listener that is equal to window->data() will be silently closed + // without asking for confirmation. It is not counted when looking for objects that + // are "blocking" changes of the query. + const bool tempListenerExists = listeners.removeAll(temp) > 0; + // Immediate success if there's no temp-data's listener to close nor other listeners to close + if (!tempListenerExists && listeners.isEmpty()) { + return true; + } + + if (!listeners.isEmpty()) { + QString openedObjectsStr = "

    "; + for(const KDbTableSchemaChangeListener* listener : listeners) { + openedObjectsStr += QString("
  • %1
  • ").arg(listener->name()); + } + openedObjectsStr += "

"; + QString message = "" + + i18nc("@info/plain Sentence1 Sentence2 Sentence3", "%1%2%3", + KexiUtils::localizedStringToHtmlSubstring(msg), openedObjectsStr, + KexiUtils::localizedStringToHtmlSubstring( + kxi18nc("@info", "Do you want to close these windows and save the " + "design or cancel saving?"))) + + ""; + KGuiItem closeAndSaveItem(KStandardGuiItem::save()); + closeAndSaveItem.setText( + xi18nc("@action:button Close all windows and save", "Close Windows and Save")); + closeAndSaveItem.setToolTip(xi18nc("@info:tooltip Close all windows and save design", + "Close all windows and save design")); + const int r = KMessageBox::questionYesNo(window, message, QString(), closeAndSaveItem, + KStandardGuiItem::cancel(), QString(), + KMessageBox::Notify | KMessageBox::Dangerous); + if (r != KMessageBox::Yes) { + return cancelled; + } + } + // try to close every window depending on the query (if present) and also the temp-data's + // listener (if present) + const tristate res = KDbTableSchemaChangeListener::closeListeners(conn, query, { temp }); + if (res != true) { //do not expose closing errors twice; just cancel + return cancelled; + } + return true; +} + //---------------- KexiQueryPartTempData::KexiQueryPartTempData(KexiWindow* window, KDbConnection *conn) : KexiWindowData(window) , KDbTableSchemaChangeListener() , m_query(0) , m_queryChangedInView(Kexi::NoViewMode) { this->conn = conn; setName(KexiUtils::localizedStringToHtmlSubstring( kxi18nc("@info", "Query %1").subs(window->partItem()->name()))); } KexiQueryPartTempData::~KexiQueryPartTempData() { KDbTableSchemaChangeListener::unregisterForChanges(conn, this); } void KexiQueryPartTempData::clearQuery() { if (!m_query) return; unregisterForTablesSchemaChanges(); m_query->clear(); } void KexiQueryPartTempData::unregisterForTablesSchemaChanges() { KDbTableSchemaChangeListener::unregisterForChanges(conn, this); } void KexiQueryPartTempData::registerTableSchemaChanges(KDbQuerySchema *q) { if (!q) return; - foreach(const KDbTableSchema* table, *q->tables()) { - KDbTableSchemaChangeListener::registerForChanges(conn, this, table); - } + KDbTableSchemaChangeListener::registerForChanges(conn, this, q); } tristate KexiQueryPartTempData::closeListener() { KexiWindow* window = static_cast(parent()); + qDebug() << window->partItem()->name(); return KexiMainWindowIface::global()->closeWindow(window); } KDbQuerySchema *KexiQueryPartTempData::takeQuery() { KDbQuerySchema *query = m_query; m_query = 0; return query; } void KexiQueryPartTempData::setQuery(KDbQuerySchema *query) { if (m_query && m_query == query) return; if (m_query /* query not owned by window */ && (static_cast(parent())->schemaObject() != static_cast(m_query))) { delete m_query; } m_query = query; } Kexi::ViewMode KexiQueryPartTempData::queryChangedInView() const { return m_queryChangedInView; } void KexiQueryPartTempData::setQueryChangedInView(bool set) { m_queryChangedInView = set ? qobject_cast(parent())->currentViewMode() : Kexi::NoViewMode; } #include "kexiquerypart.moc" diff --git a/src/plugins/queries/kexiquerypart.h b/src/plugins/queries/kexiquerypart.h index bcc5bd14e..bd827cb3d 100644 --- a/src/plugins/queries/kexiquerypart.h +++ b/src/plugins/queries/kexiquerypart.h @@ -1,118 +1,150 @@ /* This file is part of the KDE project Copyright (C) 2003 Lucijan Busch - Copyright (C) 2004-2016 Jarosław Staniek + Copyright (C) 2004-2017 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 KEXIQUERYPART_H #define KEXIQUERYPART_H #include #include #include #include #include //! @short Temporary data kept in memory while switching between Query Window's views class KexiQueryPartTempData : public KexiWindowData, public KDbTableSchemaChangeListener { Q_OBJECT public: KexiQueryPartTempData(KexiWindow* parent, KDbConnection *conn); virtual ~KexiQueryPartTempData(); virtual tristate closeListener(); void clearQuery(); void unregisterForTablesSchemaChanges(); void registerTableSchemaChanges(KDbQuerySchema *q); /*! Assigns query \a query for this data. Existing query (available using query()) is deleted but only if it is not owned by parent window (i.e. != KexiWindow::schemaObject()). \a query can be 0. If \a query is equal to existing query, nothing is performed. */ void setQuery(KDbQuerySchema *query); //! \return query associated with this data KDbQuerySchema *query() const { return m_query; } //! Takes query associated with this data (without deleting) and returns it. //! After this call query() == 0 KDbQuerySchema *takeQuery(); //! Connection used for retrieving definition of the query KDbConnection *conn; /*! @return view mode if which the query member has changed. It's possibly one of previously visited views. Kexi::NoViewMode is the default, what means that query was not changed. Used on view switching. We're checking this flag to see if we should rebuild internal structure for DesignViewMode of regenerated sql text in TextViewMode after switch from other view. */ Kexi::ViewMode queryChangedInView() const; /*! Sets the queryChangedInView flag. If @a set is true, then the flag is changed to the current view mode. If @a set is false, the flag is changed to Kexi::NoViewMode. @see queryChangedInView() */ void setQueryChangedInView(bool set); private: KDbQuerySchema *m_query; Kexi::ViewMode m_queryChangedInView; }; //! @short Kexi Query Designer plugin class KexiQueryPart : public KexiPart::Part { Q_OBJECT public: KexiQueryPart(QObject *parent, const QVariantList &); virtual ~KexiQueryPart(); virtual tristate remove(KexiPart::Item *item); //! Implemented for KexiPart::Part. virtual KDbQuerySchema* currentQuery(KexiView* view); virtual KLocalizedString i18nMessage(const QString& englishMessage, KexiWindow* window) const; /*! Renames stored data pointed by \a item to \a newName. Reimplemented to mark the query obsolete by using KDbConnection::setQuerySchemaObsolete(). */ virtual tristate rename(KexiPart::Item *item, const QString& newName); + /** + * Closes objects that listenen to changes of the query schema @a query, i.e. use it. + * + * These objects can be currently: + * - lookup fields of tables + * - queries using the query directly (as subqueries) or via lookup fields + * - forms and reports that use the query directly as data source or via query. + * + * Scripts referencing the query programatically are not analyzed, so they can fail on next + * execution. + * + * This method asks the user for approval if there is at least one object that listens for + * changes of the schema (altering or removal). If there is no approval, returns + * @c cancelled. On failure @c false is returned. If @a window is @c nullptr, @c true is + * returned immediately because there is no window to care about. + * + * @note Unlike renaming tables, renaming queries just marks the previous query object one as + * "obsolete" using KDbConnection::setQuerySchemaObsolete() and keeps the existing actual object + * in memory so there is no risk of accessing deleted object by other objects. + * + * Special case: listener for the query @a query will be silently closed without asking for + * confirmation. It is ignored when looking for objects that are "blocking" changes + * of @a query. This exception is needed because the listener handles the data view's lifetime + * and the data view should be reset silently without bothering the user. + * + * @see KexiQueryPartTempData::closeListener() + * @see KexiTablePart::askForClosingObjectsUsingTableSchema() + */ + static tristate askForClosingObjectsUsingQuerySchema(KexiWindow *window, KDbConnection *conn, + KDbQuerySchema *query, + const KLocalizedString &msg); + protected: KexiWindowData* createWindowData(KexiWindow* window) override Q_REQUIRED_RESULT; KexiView *createView(QWidget *parent, KexiWindow *window, KexiPart::Item *item, Kexi::ViewMode viewMode = Kexi::DataViewMode, QMap *staticObjectArgs = nullptr) override Q_REQUIRED_RESULT; virtual void initPartActions(); virtual void initInstanceActions(); virtual KDbObject* loadSchemaObject(KexiWindow *window, const KDbObject& object, Kexi::ViewMode viewMode, bool *ownedByWindow); }; #endif diff --git a/src/plugins/reports/KexiDBReportDataSource.cpp b/src/plugins/reports/KexiDBReportDataSource.cpp index ed38d0b64..dfe92268a 100644 --- a/src/plugins/reports/KexiDBReportDataSource.cpp +++ b/src/plugins/reports/KexiDBReportDataSource.cpp @@ -1,326 +1,333 @@ /* * Kexi Report Plugin * Copyright (C) 2007-2017 by Adam Pigg +* Copyright (C) 2017 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 "KexiDBReportDataSource.h" -#include +#include "kexireportpart.h" #include #include #include #include #include #include class Q_DECL_HIDDEN KexiDBReportDataSource::Private { public: - explicit Private(KDbConnection *pDb) - : cursor(0), connection(pDb), originalSchema(0), copySchema(0) + explicit Private(KexiReportPartTempData *data) + : cursor(0), tempData(data), originalSchema(0), copySchema(0) { } ~Private() { delete copySchema; delete originalSchema; } QString objectName; KDbCursor *cursor; - KDbConnection *connection; + KexiReportPartTempData *tempData; KDbQuerySchema *originalSchema; KDbQuerySchema *copySchema; }; -KexiDBReportDataSource::KexiDBReportDataSource(const QString& objectName, - const QString& pluginId, - KDbConnection* pDb) - : d(new Private(pDb)) +KexiDBReportDataSource::KexiDBReportDataSource(const QString &objectName, const QString &pluginId, + KexiReportPartTempData *data) + : d(new Private(data)) { d->objectName = objectName; getSchema(pluginId); } void KexiDBReportDataSource::setSorting(const QList& sorting) { if (d->copySchema) { if (sorting.isEmpty()) return; KDbOrderByColumnList order; for (int i = 0; i < sorting.count(); i++) { - if (!order.appendField(d->connection, d->copySchema, sorting[i].field(), + if (!order.appendField(d->tempData->connection(), d->copySchema, sorting[i].field(), KDbOrderByColumn::fromQt(sorting[i].order()))) { qWarning() << "Cannot set sort field" << i << sorting[i].field(); return; } } d->copySchema->setOrderByColumnList(order); } else { qWarning() << "Unable to sort null schema"; } } void KexiDBReportDataSource::addCondition(const QString &field, const QVariant &value, const QString& relation) { if (d->copySchema) { KDbField *fld = d->copySchema->findTableField(field); if (fld) { if (relation.length() == 1) { QString errorMessage; QString errorDescription; if (!d->copySchema->addToWhereExpression(fld, value, KDbToken(relation.toLatin1()[0]), &errorMessage, &errorDescription)) { qWarning() << "Invalid expression cannot be added to WHERE:" << fld << relation << value; qWarning() << "addToWhereExpression() failed, message=" << errorMessage << "description=" << errorDescription; } } else { qWarning() << "Invalid relation passed in:" << relation; } } } else { qDebug() << "Unable to add expresstion to null schema"; } } KexiDBReportDataSource::~KexiDBReportDataSource() { close(); delete d; } bool KexiDBReportDataSource::open() { - if ( d->connection && d->cursor == 0 ) + if ( d->tempData->connection() && d->cursor == 0 ) { if ( d->objectName.isEmpty() ) { return false; } else if ( d->copySchema) { qDebug() << "Opening cursor.." - << KDbConnectionAndQuerySchema(d->connection, *d->copySchema); - d->cursor = d->connection->executeQuery(d->copySchema, KDbCursor::Option::Buffered); + << KDbConnectionAndQuerySchema(d->tempData->connection(), *d->copySchema); + d->cursor = d->tempData->connection()->executeQuery(d->copySchema, KDbCursor::Option::Buffered); } if ( d->cursor ) { qDebug() << "Moving to first record.."; return d->cursor->moveFirst(); } else return false; } return false; } bool KexiDBReportDataSource::close() { if (d->cursor) { const bool ok = d->cursor->close(); - d->connection->deleteCursor(d->cursor); + d->tempData->connection()->deleteCursor(d->cursor); d->cursor = nullptr; return ok; } return true; } bool KexiDBReportDataSource::getSchema(const QString& pluginId) { - if (d->connection) - { + if (d->tempData->connection()) { + KDbTableSchemaChangeListener::unregisterForChanges(d->tempData->connection(), d->tempData); delete d->originalSchema; d->originalSchema = 0; delete d->copySchema; d->copySchema = 0; + KDbTableSchema *table = nullptr; + KDbQuerySchema *query = nullptr; if ((pluginId.isEmpty() || pluginId == "org.kexi-project.table") - && d->connection->tableSchema(d->objectName)) + && (table = d->tempData->connection()->tableSchema(d->objectName))) { qDebug() << d->objectName << "is a table.."; - d->originalSchema = new KDbQuerySchema(d->connection->tableSchema(d->objectName)); + d->originalSchema = new KDbQuerySchema(table); } else if ((pluginId.isEmpty() || pluginId == "org.kexi-project.query") - && d->connection->querySchema(d->objectName)) + && (query = d->tempData->connection()->querySchema(d->objectName))) { qDebug() << d->objectName << "is a query.."; - qDebug() << KDbConnectionAndQuerySchema(d->connection, - *d->connection->querySchema(d->objectName)); - d->originalSchema - = new KDbQuerySchema(*(d->connection->querySchema(d->objectName)), d->connection); + qDebug() << KDbConnectionAndQuerySchema(d->tempData->connection(), *query); + d->originalSchema = new KDbQuerySchema(*query, d->tempData->connection()); } if (d->originalSchema) { - const KDbNativeStatementBuilder builder(d->connection, KDb::DriverEscaping); + const KDbNativeStatementBuilder builder(d->tempData->connection(), KDb::DriverEscaping); KDbEscapedString sql; if (builder.generateSelectStatement(&sql, d->originalSchema)) { qDebug() << "Original:" << sql; } else { qDebug() << "Original: ERROR"; + return false; } - qDebug() << KDbConnectionAndQuerySchema(d->connection, *d->originalSchema); + qDebug() << KDbConnectionAndQuerySchema(d->tempData->connection(), *d->originalSchema); - d->copySchema = new KDbQuerySchema(*d->originalSchema, d->connection); - qDebug() << KDbConnectionAndQuerySchema(d->connection, *d->copySchema); + d->copySchema = new KDbQuerySchema(*d->originalSchema, d->tempData->connection()); + qDebug() << KDbConnectionAndQuerySchema(d->tempData->connection(), *d->copySchema); if (builder.generateSelectStatement(&sql, d->copySchema)) { qDebug() << "Copy:" << sql; } else { qDebug() << "Copy: ERROR"; + return false; + } + if (table) { + KDbTableSchemaChangeListener::registerForChanges(d->tempData->connection(), d->tempData, table); + } else if (query) { + KDbTableSchemaChangeListener::registerForChanges(d->tempData->connection(), d->tempData, query); } } return true; } return false; } QString KexiDBReportDataSource::sourceName() const { return d->objectName; } int KexiDBReportDataSource::fieldNumber ( const QString &fld ) const { if (!d->cursor || !d->cursor->query()) { return -1; } const KDbQueryColumnInfo::Vector fieldsExpanded(d->cursor->query()->fieldsExpanded( - d->connection, KDbQuerySchema::FieldsExpandedMode::Unique)); + d->tempData->connection(), KDbQuerySchema::FieldsExpandedMode::Unique)); for (int i = 0; i < fieldsExpanded.size(); ++i) { if (0 == QString::compare(fld, fieldsExpanded[i]->aliasOrName(), Qt::CaseInsensitive)) { return i; } } return -1; } QStringList KexiDBReportDataSource::fieldNames() const { if (!d->originalSchema) { return QStringList(); } QStringList names; const KDbQueryColumnInfo::Vector fieldsExpanded(d->originalSchema->fieldsExpanded( - d->connection, KDbQuerySchema::FieldsExpandedMode::Unique)); + d->tempData->connection(), KDbQuerySchema::FieldsExpandedMode::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 KexiDBReportDataSource::value (int i) const { if ( d->cursor ) return d->cursor->value ( i ); return QVariant(); } QVariant KexiDBReportDataSource::value ( const QString &fld ) const { int i = fieldNumber ( fld ); if (d->cursor && i >= 0) return d->cursor->value ( i ); return QVariant(); } bool KexiDBReportDataSource::moveNext() { if ( d->cursor ) return d->cursor->moveNext(); return false; } bool KexiDBReportDataSource::movePrevious() { if ( d->cursor ) return d->cursor->movePrev(); return false; } bool KexiDBReportDataSource::moveFirst() { if ( d->cursor ) return d->cursor->moveFirst(); return false; } bool KexiDBReportDataSource::moveLast() { if ( d->cursor ) return d->cursor->moveLast(); return false; } qint64 KexiDBReportDataSource::at() const { if ( d->cursor ) return d->cursor->at(); return 0; } qint64 KexiDBReportDataSource::recordCount() const { if (d->copySchema) { - return d->connection->recordCount(d->copySchema); + return d->tempData->connection()->recordCount(d->copySchema); } return 1; } QStringList KexiDBReportDataSource::dataSourceNames() const { //Get the list of queries in the database QStringList qs; - if (d->connection && d->connection->isConnected()) { - QList tids = d->connection->tableIds(); + if (d->tempData->connection() && d->tempData->connection()->isConnected()) { + QList tids = d->tempData->connection()->tableIds(); qs << ""; for (int i = 0; i < tids.size(); ++i) { - KDbTableSchema* tsc = d->connection->tableSchema(tids[i]); + KDbTableSchema* tsc = d->tempData->connection()->tableSchema(tids[i]); if (tsc) qs << tsc->name(); } - QList qids = d->connection->queryIds(); + QList qids = d->tempData->connection()->queryIds(); qs << ""; for (int i = 0; i < qids.size(); ++i) { - KDbQuerySchema* qsc = d->connection->querySchema(qids[i]); + KDbQuerySchema* qsc = d->tempData->connection()->querySchema(qids[i]); if (qsc) qs << qsc->name(); } } return qs; } KReportDataSource* KexiDBReportDataSource::create(const QString& source) const { - return new KexiDBReportDataSource(source, QString(), d->connection); + return new KexiDBReportDataSource(source, QString(), d->tempData); } diff --git a/src/plugins/reports/KexiDBReportDataSource.h b/src/plugins/reports/KexiDBReportDataSource.h index ef7861399..18342e25b 100644 --- a/src/plugins/reports/KexiDBReportDataSource.h +++ b/src/plugins/reports/KexiDBReportDataSource.h @@ -1,78 +1,81 @@ /* * Kexi Report Plugin * Copyright (C) 2007-2017 by Adam Pigg +* Copyright (C) 2017 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 +class KexiReportPartTempData; + //! @brief Implementation of database report data source class KexiDBReportDataSource : public KReportDataSource { public: /*! * @a pluginId specifies type of @a objectName, a table or query. * Types accepted: * -"org.kexi-project.table" * -"org.kexi-project.query" * -empty QString() - attempt to resolve @a objectName */ - KexiDBReportDataSource(const QString &objectName, const QString& pluginId, KDbConnection *conn); + KexiDBReportDataSource(const QString &objectName, const QString &pluginId, + KexiReportPartTempData *data); virtual ~KexiDBReportDataSource(); virtual QStringList fieldNames() const; virtual void setSorting(const QList& sorting); //! Adds a condition to the data source. //! @note Only single-character relation operators such as "=" or ">" are supported now. //! @todo Use KDb parser to support all relation operators such as ">=". virtual void addCondition(const QString &field, const QVariant &value, const QString &relation = QLatin1String("=")); virtual QString sourceName() const; virtual int fieldNumber(const QString &field) const; virtual QVariant value(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 dataSourceNames() const; virtual KReportDataSource* create(const QString& source) const Q_REQUIRED_RESULT; private: class Private; Private * const d; bool getSchema(const QString& pluginId); }; #endif diff --git a/src/plugins/reports/kexireportdesignview.cpp b/src/plugins/reports/kexireportdesignview.cpp index 597be41d6..5a6c1d3d5 100644 --- a/src/plugins/reports/kexireportdesignview.cpp +++ b/src/plugins/reports/kexireportdesignview.cpp @@ -1,215 +1,246 @@ /* * Kexi Report Plugin * Copyright (C) 2007-2009 by Adam Pigg -* Copyright (C) 2011 Jarosław Staniek +* Copyright (C) 2011-2017 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 "kexireportdesignview.h" #include #include #include "kexisourceselector.h" #include +#include #include #include #include #include #include #include KexiReportDesignView::KexiReportDesignView(QWidget *parent, KexiSourceSelector *s) : KexiView(parent) { m_scrollArea = new QScrollArea(this); layout()->addWidget(m_scrollArea); m_sourceSelector = s; m_reportDesigner = 0; m_editCutAction = KStandardAction::cut(this); m_editCutAction->setProperty("iconOnly", true); m_editCopyAction = KStandardAction::copy(this); m_editCopyAction->setProperty("iconOnly", true); m_editPasteAction = KStandardAction::paste(this); m_editPasteAction->setProperty("iconOnly", true); const KGuiItem del = KStandardGuiItem::del(); m_editDeleteAction = new QAction(del.icon(), del.text(), this); m_editDeleteAction->setObjectName("editdelete"); m_editDeleteAction->setToolTip(del.toolTip()); m_editDeleteAction->setWhatsThis(del.whatsThis()); m_editDeleteAction->setProperty("iconOnly", true); m_editSectionAction = new QAction(xi18n("Edit Sections"), this); m_editSectionAction->setObjectName("sectionedit"); m_itemRaiseAction = new QAction(koIcon("object-order-front"), xi18n("Raise"), this); m_itemRaiseAction->setObjectName("itemraise"); m_itemLowerAction = new QAction(koIcon("object-order-back"), xi18n("Lower"), this); m_itemLowerAction->setObjectName("itemlower"); QList al; QAction *sep = new QAction(QString(), this); sep->setSeparator(true); al << m_editCutAction << m_editCopyAction << m_editPasteAction << m_editDeleteAction << sep << m_editSectionAction << sep << m_itemLowerAction << m_itemRaiseAction; setViewActions(al); } KexiReportDesignView::~KexiReportDesignView() { } KPropertySet *KexiReportDesignView::propertySet() { return m_reportDesigner->selectedItemPropertySet(); } void KexiReportDesignView::slotDesignerPropertySetChanged() { propertySetReloaded(true); propertySetSwitched(); } KDbObject* KexiReportDesignView::storeNewData(const KDbObject& object, KexiView::StoreNewDataOptions options, bool *cancel) { KDbObject *s = KexiView::storeNewData(object, options, cancel); if (!s || *cancel) { delete s; return 0; } qDebug() << "new id:" << s->id(); if (!storeData()) { //failure: remove object's object data to avoid garbage KDbConnection *conn = KexiMainWindowIface::global()->project()->dbConnection(); conn->removeObject(s->id()); delete s; return 0; } return s; } tristate KexiReportDesignView::storeData(bool dontAsk) { Q_UNUSED(dontAsk); QDomDocument doc("kexireport"); QDomElement root = doc.createElement("kexireport"); - QDomElement conndata = m_sourceSelector->connectionData(); + QDomElement conndata = connectionData(); if (conndata.isNull()) qDebug() << "Null conn data!"; root.appendChild(m_reportDesigner->document()); root.appendChild(conndata); doc.appendChild(root); QString src = doc.toString(); qDebug() << src; if (storeDataBlock(src, "layout")) { qDebug() << "Saved OK"; setDirty(false); return true; } qDebug() << "NOT Saved OK"; return false; } tristate KexiReportDesignView::beforeSwitchTo(Kexi::ViewMode mode, bool *dontStore) { qDebug() << mode; *dontStore = true; if (m_reportDesigner && mode == Kexi::DataViewMode) { qDebug() << "Saving temp data"; tempData()->reportDefinition = m_reportDesigner->document(); qDebug() << m_reportDesigner->document().toDocument().toString(); tempData()->reportSchemaChangedInPreviousView = true; } return true; } tristate KexiReportDesignView::afterSwitchFrom(Kexi::ViewMode mode) { Q_UNUSED(mode); if (tempData()->reportDefinition.isNull()) { m_reportDesigner = new KReportDesigner(this); } else { if (m_reportDesigner) { m_scrollArea->takeWidget(); delete m_reportDesigner; m_reportDesigner = 0; } m_reportDesigner = new KReportDesigner(this, tempData()->reportDefinition); - m_sourceSelector->setConnectionData(tempData()->connectionDefinition); + setConnectionData(tempData()->connectionDefinition); m_reportDesigner->setScriptSource(qobject_cast(part())); } connect(m_reportDesigner, SIGNAL(itemInserted(QString)), this, SIGNAL(itemInserted(QString))); m_scrollArea->setWidget(m_reportDesigner); connect(m_reportDesigner, SIGNAL(propertySetChanged()), this, SLOT(slotDesignerPropertySetChanged())); connect(m_reportDesigner, SIGNAL(dirty()), this, SLOT(setDirty())); //Added default keyboard shortcuts for the actions QShortcut *cutShortcut = new QShortcut(QKeySequence(QKeySequence::Cut), m_reportDesigner); QShortcut *copyShortcut = new QShortcut(QKeySequence(QKeySequence::Copy), m_reportDesigner); QShortcut *pasteShortcut = new QShortcut(QKeySequence(QKeySequence::Paste), m_reportDesigner); QShortcut *deleteShortcut = new QShortcut(QKeySequence(QKeySequence::Delete), m_reportDesigner); connect(cutShortcut, SIGNAL(activated()), m_reportDesigner, SLOT(slotEditCut())); connect(copyShortcut, SIGNAL(activated()), m_reportDesigner, SLOT(slotEditCopy())); connect(pasteShortcut, SIGNAL(activated()), m_reportDesigner, SLOT(slotEditPaste())); connect(deleteShortcut, SIGNAL(activated()), m_reportDesigner, SLOT(slotEditDelete())); //Edit Actions connect(m_editCutAction, SIGNAL(triggered()), m_reportDesigner, SLOT(slotEditCut())); connect(m_editCopyAction, SIGNAL(triggered()), m_reportDesigner, SLOT(slotEditCopy())); connect(m_editPasteAction, SIGNAL(triggered()), m_reportDesigner, SLOT(slotEditPaste())); connect(m_editDeleteAction, SIGNAL(triggered()), m_reportDesigner, SLOT(slotEditDelete())); connect(m_editSectionAction, SIGNAL(triggered()), m_reportDesigner, SLOT(slotSectionEditor())); //Raise/Lower connect(m_itemRaiseAction, SIGNAL(triggered()), m_reportDesigner, SLOT(slotRaiseSelected())); connect(m_itemLowerAction, SIGNAL(triggered()), m_reportDesigner, SLOT(slotLowerSelected())); return true; } KexiReportPartTempData* KexiReportDesignView::tempData() const { return static_cast(window()->data()); } void KexiReportDesignView::slotDataSourceChanged() { - m_reportDesigner->setDataSource(m_sourceSelector->createDataSource()); - tempData()->connectionDefinition = m_sourceSelector->connectionData(); + if (m_sourceSelector->isSelectionValid()) { + m_reportDesigner->setDataSource(new KexiDBReportDataSource( + m_sourceSelector->selectedName(), m_sourceSelector->selectedPluginId(), tempData())); + tempData()->connectionDefinition = connectionData(); + } else { + m_reportDesigner->setDataSource(nullptr); + tempData()->connectionDefinition = QDomElement(); + } setDirty(true); } void KexiReportDesignView::triggerAction(const QString &action) { m_reportDesigner->slotItem(action); } + +QDomElement KexiReportDesignView::connectionData() const +{ + QDomDocument dd; + QDomElement conndata = dd.createElement("connection"); + conndata.setAttribute("type", "internal"); // for backward compatibility, currently always + // internal, we used to have "external" in old Kexi + conndata.setAttribute("source", m_sourceSelector->selectedName()); + conndata.setAttribute("class", m_sourceSelector->selectedPluginId()); + return conndata; +} + +void KexiReportDesignView::setConnectionData(const QDomElement &c) +{ + qDebug() << c; + if (c.attribute("type") == "internal") { + QString sourceClass(c.attribute("class")); + if (sourceClass != "org.kexi-project.table" && sourceClass != "org.kexi-project.query") { + sourceClass.clear(); // KexiDataSourceComboBox will try to find table, then query + } + m_sourceSelector->setDataSource(sourceClass, c.attribute("source")); + slotDataSourceChanged(); + } +} diff --git a/src/plugins/reports/kexireportdesignview.h b/src/plugins/reports/kexireportdesignview.h index 71ec90e06..795b030cd 100644 --- a/src/plugins/reports/kexireportdesignview.h +++ b/src/plugins/reports/kexireportdesignview.h @@ -1,81 +1,84 @@ /* * Kexi Report Plugin * Copyright (C) 2007-2009 by Adam Pigg -* Copyright (C) 2011 Jarosław Staniek +* Copyright (C) 2011-2017 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 KEXIREPORTDESIGNVIEW_H #define KEXIREPORTDESIGNVIEW_H #include #include #include #include #include class QScrollArea; class KexiSourceSelector; /** */ class KexiReportDesignView : public KexiView { Q_OBJECT public: KexiReportDesignView(QWidget *parent, KexiSourceSelector*); ~KexiReportDesignView(); virtual tristate afterSwitchFrom(Kexi::ViewMode mode); virtual tristate beforeSwitchTo(Kexi::ViewMode mode, bool *dontStore); void triggerAction(const QString &); Q_SIGNALS: void itemInserted(const QString& entity); private: - KReportDesigner *m_reportDesigner; KexiReportPartTempData* tempData() const; + QDomElement connectionData() const; + void setConnectionData(const QDomElement &c); + + KReportDesigner *m_reportDesigner; QScrollArea * m_scrollArea; //Actions QAction *m_editCutAction; QAction *m_editCopyAction; QAction *m_editPasteAction; QAction *m_editDeleteAction; QAction *m_editSectionAction; QAction *m_itemRaiseAction; QAction *m_itemLowerAction; KexiSourceSelector *m_sourceSelector; protected: virtual KPropertySet *propertySet(); virtual tristate storeData(bool dontAsk = false); virtual KDbObject* storeNewData(const KDbObject& object, KexiView::StoreNewDataOptions options, bool *cancel); private Q_SLOTS: void slotDesignerPropertySetChanged(); public Q_SLOTS: void slotDataSourceChanged(); }; #endif diff --git a/src/plugins/reports/kexireportpart.cpp b/src/plugins/reports/kexireportpart.cpp index ce73d13ec..c75554c92 100644 --- a/src/plugins/reports/kexireportpart.cpp +++ b/src/plugins/reports/kexireportpart.cpp @@ -1,303 +1,338 @@ /* * Kexi Report Plugin * Copyright (C) 2007-2008 by Adam Pigg - * Copyright (C) 2011-2015 Jarosław Staniek + * Copyright (C) 2011-2017 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 "kexireportpart.h" #include #include #include #include #include #include #include "kexireportview.h" #include "kexireportdesignview.h" #include #include "kexisourceselector.h" #include +#include //! @internal class Q_DECL_HIDDEN KexiReportPart::Private { public: Private() : toolboxActionGroup(0) { sourceSelector = 0; } ~Private() { } KexiSourceSelector *sourceSelector; QActionGroup toolboxActionGroup; QMap toolboxActionsByName; }; static bool isInterpreterSupported(const QString &interpreterName) { return 0 == interpreterName.compare(QLatin1String("javascript"), Qt::CaseInsensitive) || 0 == interpreterName.compare(QLatin1String("qtscript"), Qt::CaseInsensitive); } KexiReportPart::KexiReportPart(QObject *parent, const QVariantList &l) : KexiPart::Part(parent, xi18nc("Translate this word using only lowercase alphanumeric characters (a..z, 0..9). " "Use '_' character instead of spaces. First character should be a..z character. " "If you cannot use latin characters in your language, use english word.", "report"), xi18nc("tooltip", "Create new report"), xi18nc("what's this", "Creates new report."), l) , d(new Private) { setInternalPropertyValue("newObjectsAreDirty", true); // needed for custom "pixmap" property editor widget KexiCustomPropertyFactory::init(); } KexiReportPart::~KexiReportPart() { delete d; } KLocalizedString KexiReportPart::i18nMessage( const QString& englishMessage, KexiWindow* window) const { Q_UNUSED(window); if (englishMessage == "Design of object %1 has been modified.") return kxi18nc(I18NC_NOOP("@info", "Design of report %1 has been modified.")); if (englishMessage == "Object %1 already exists.") return kxi18nc(I18NC_NOOP("@info", "Report %1 already exists.")); return Part::i18nMessage(englishMessage, window); } KexiView* KexiReportPart::createView(QWidget *parent, KexiWindow* window, KexiPart::Item *item, Kexi::ViewMode viewMode, QMap*) { Q_ASSERT(item); Q_UNUSED(window); Q_UNUSED(item); KexiView* view = 0; if (viewMode == Kexi::DataViewMode) { view = new KexiReportView(parent); } else if (viewMode == Kexi::DesignViewMode) { view = new KexiReportDesignView(parent, d->sourceSelector); connect(d->sourceSelector, &KexiSourceSelector::dataSourceChanged, qobject_cast(view), &KexiReportDesignView::slotDataSourceChanged); connect(view, SIGNAL(itemInserted(QString)), this, SLOT(slotItemInserted(QString))); } return view; } void KexiReportPart::initPartActions() { KexiMainWindowIface *win = KexiMainWindowIface::global(); QList reportActions = KReportDesigner::itemActions(&d->toolboxActionGroup); foreach(QAction* action, reportActions) { connect(action, SIGNAL(triggered(bool)), this, SLOT(slotToolboxActionTriggered(bool))); win->addToolBarAction("report", action); d->toolboxActionsByName.insert(action->objectName(), action); } } KDbObject* KexiReportPart::loadSchemaObject( KexiWindow *window, const KDbObject& object, Kexi::ViewMode viewMode, bool *ownedByWindow) { QString layout; if ( !loadDataBlock(window, &layout, "layout") == true && !loadDataBlock(window, &layout, "pgzreport_layout") == true /* compat */) { return 0; } QDomDocument doc; if (!doc.setContent(layout)) { return 0; } KexiReportPartTempData * temp = static_cast(window->data()); const QDomElement root = doc.documentElement(); temp->reportDefinition = root.firstChildElement("report:content"); if (temp->reportDefinition.isNull()) { qWarning() << "no report report:content element found in report" << window->partItem()->name(); return 0; } temp->connectionDefinition = root.firstChildElement("connection"); if (temp->connectionDefinition.isNull()) { qWarning() << "no report report:content element found in report" << window->partItem()->name(); return 0; } return KexiPart::Part::loadSchemaObject(window, object, viewMode, ownedByWindow); } KexiWindowData* KexiReportPart::createWindowData(KexiWindow* window) { - return new KexiReportPartTempData(window); + KexiMainWindowIface *win = KexiMainWindowIface::global(); + return new KexiReportPartTempData(window, win->project()->dbConnection()); } -KexiReportPartTempData::KexiReportPartTempData(KexiWindow* parent) +//---------------- + +class Q_DECL_HIDDEN KexiReportPartTempData::Private +{ +public: + Private() + { + } + KDbConnection *conn; +}; + +KexiReportPartTempData::KexiReportPartTempData(KexiWindow* parent, KDbConnection *conn) : KexiWindowData(parent) , reportSchemaChangedInPreviousView(true /*to force reloading on startup*/) + , d(new Private) +{ + d->conn = conn; + setName(KexiUtils::localizedStringToHtmlSubstring( + kxi18nc("@info", "Report %1").subs(parent->partItem()->name()))); +} + +KexiReportPartTempData::~KexiReportPartTempData() +{ + KDbTableSchemaChangeListener::unregisterForChanges(d->conn, this); + delete d; +} + +KDbConnection* KexiReportPartTempData::connection() +{ + return d->conn; +} + +tristate KexiReportPartTempData::closeListener() { + KexiWindow* window = static_cast(parent()); + qDebug() << window->partItem()->name(); + return KexiMainWindowIface::global()->closeWindow(window); } void KexiReportPart::setupCustomPropertyPanelTabs(QTabWidget *tab) { if (!d->sourceSelector) { d->sourceSelector = new KexiSourceSelector(KexiMainWindowIface::global()->project(), tab); } tab->addTab(d->sourceSelector, koIcon("server-database"), QString()); tab->setTabToolTip(tab->indexOf(d->sourceSelector), xi18n("Data Source")); } void KexiReportPart::slotToolboxActionTriggered(bool checked) { if (!checked) return; QObject *theSender = sender(); if (!theSender) return; QString senderName = sender()->objectName(); KexiMainWindowIface *mainwin = KexiMainWindowIface::global(); KexiWindow *win = mainwin->currentWindow(); if (!win) return; KexiView *designView = win->viewForMode(Kexi::DesignViewMode); if (designView) { KexiReportDesignView *dv = dynamic_cast(designView); if (!dv) return; dv->triggerAction(senderName); } } void KexiReportPart::slotItemInserted(const QString& entity) { Q_UNUSED(entity); // uncheck toolbox action after it is used QAction * a = d->toolboxActionGroup.checkedAction(); if (a) { a->setChecked(false); } } QStringList KexiReportPart::scriptList() const { QStringList scripts; KexiMainWindowIface *win = KexiMainWindowIface::global(); if (win->project() && win->project()->dbConnection()) { QList scriptids = win->project()->dbConnection()->objectIds(KexiPart::ScriptObjectType); QStringList scriptnames = win->project()->dbConnection()->objectNames(KexiPart::ScriptObjectType); qDebug() << scriptids << scriptnames; int i = 0; foreach(int id, scriptids) { qDebug() << "ID:" << id; tristate res; QString script; res = win->project()->dbConnection()->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 (scriptelem.attribute("scripttype") == "object" && isInterpreterSupported(scriptelem.attribute("language"))) { scripts << scriptnames[i]; } } else { qWarning() << "Unable to parse script"; } } else { qWarning() << "Unable to loadDataBlock"; } ++i; } qDebug() << scripts; } return scripts; } QString KexiReportPart::scriptCode(const QString& scriptname) const { QString scripts; KexiMainWindowIface *win = KexiMainWindowIface::global(); if (win->project() && win->project()->dbConnection()) { QList scriptids = win->project()->dbConnection()->objectIds(KexiPart::ScriptObjectType); QStringList scriptnames = win->project()->dbConnection()->objectNames(KexiPart::ScriptObjectType); int i = 0; foreach(int id, scriptids) { qDebug() << "ID:" << id; tristate res; QString script; res = win->project()->dbConnection()->loadDataBlock(id, &script, QString()); if (res == true) { QDomDocument domdoc; bool parsed = domdoc.setContent(script, false); if (! parsed) { qWarning() << "XML parsing error"; return QString(); } QDomElement scriptelem = domdoc.namedItem("script").toElement(); if (scriptelem.isNull()) { qWarning() << "script domelement is null"; return QString(); } QString interpretername = scriptelem.attribute("language"); qDebug() << scriptelem.attribute("scripttype"); qDebug() << scriptname << scriptnames[i]; if ((isInterpreterSupported(interpretername) && scriptelem.attribute("scripttype") == "module") || scriptname == scriptnames[i]) { scripts += '\n' + scriptelem.text().toUtf8(); } ++i; } else { qWarning() << "Unable to loadDataBlock"; } } } return scripts; } diff --git a/src/plugins/reports/kexireportpart.h b/src/plugins/reports/kexireportpart.h index f80cb0dfb..1cac0ae4a 100644 --- a/src/plugins/reports/kexireportpart.h +++ b/src/plugins/reports/kexireportpart.h @@ -1,90 +1,106 @@ /* * Kexi Report Plugin * Copyright (C) 2007-2008 by Adam Pigg - * Copyright (C) 2011-2015 Jarosław Staniek + * Copyright (C) 2011-2017 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 _KEXIREPORTPART_H_ #define _KEXIREPORTPART_H_ #include #include #include #include + +#include + #include -class KexiReportPartTempData : public KexiWindowData +class KexiReportPartTempData : public KexiWindowData, public KDbTableSchemaChangeListener { Q_OBJECT public: - explicit KexiReportPartTempData(KexiWindow* parent); + KexiReportPartTempData(KexiWindow* parent, KDbConnection *conn); + ~KexiReportPartTempData(); QDomElement reportDefinition; QDomElement connectionDefinition; /*! true, if \a document member has changed in previous view. Used on view switching. Check this flag to see if we should refresh data for DataViewMode. */ bool reportSchemaChangedInPreviousView; + + KDbConnection *connection(); + +protected: + //! This temp-data acts as a listener for tracking changes in table schema + //! used by the report. This method closes the report on request. + tristate closeListener() override; + +private: + Q_DISABLE_COPY(KexiReportPartTempData) + class Private; + Private * const d; }; /** * @short Application Main Window */ class KexiReportPart : public KexiPart::Part, public KReportScriptSource { Q_OBJECT public: /** * Default Constructor */ KexiReportPart(QObject *parent, const QVariantList &l); /** * Default Destructor */ virtual ~KexiReportPart(); virtual void setupCustomPropertyPanelTabs(QTabWidget *tab); virtual KLocalizedString i18nMessage(const QString& englishMessage, KexiWindow* window) const; QStringList scriptList() const override; QString scriptCode(const QString& script) const override; protected: KexiView *createView(QWidget *parent, KexiWindow *win, KexiPart::Item *item, Kexi::ViewMode = Kexi::DataViewMode, QMap *staticObjectArgs = nullptr) override Q_REQUIRED_RESULT; KexiWindowData* createWindowData(KexiWindow* window) override Q_REQUIRED_RESULT; virtual void initPartActions(); virtual KDbObject* loadSchemaObject(KexiWindow *window, const KDbObject& object, Kexi::ViewMode viewMode, bool *ownedByWindow); private Q_SLOTS: void slotToolboxActionTriggered(bool checked); //! Unchecks toolbox action for @a entity after it is used. void slotItemInserted(const QString& entity); private: class Private; Private* d; }; #endif // _KEXIREPORTPART_H_ diff --git a/src/plugins/reports/kexireportview.cpp b/src/plugins/reports/kexireportview.cpp index 248cbaefd..61dcaff1e 100644 --- a/src/plugins/reports/kexireportview.cpp +++ b/src/plugins/reports/kexireportview.cpp @@ -1,492 +1,476 @@ /* * Kexi Report Plugin * Copyright (C) 2007-2008 by Adam Pigg (adam@piggz.co.uk) * Copyright (C) 2014-2017 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 "KexiDBReportDataSource.h" #ifndef KEXI_MOBILE #include - //! @todo KEXI3 -#if 0 -#include "keximigratereportdata.h" -#endif #endif #include #include #include //! @todo KEXI3 #include "../scripting/kexiscripting/kexiscriptadaptor.h" #include #include #include #include #include "krscriptfunctions.h" #include #include #include #include #include #include #include #include #include #include #include #include KexiReportView::KexiReportView(QWidget *parent) : KexiView(parent), m_preRenderer(0), m_functions(0) //! @todo KEXI3, m_kexi(0) { setObjectName("KexiReportDesigner_DataView"); m_reportView = new KReportView(this); layout()->addWidget(m_reportView); #ifndef KEXI_MOBILE m_pageSelector = new KexiRecordNavigator(*m_reportView->scrollArea(), m_reportView); m_pageSelector->setInsertingButtonVisible(false); m_pageSelector->setInsertingEnabled(false); m_pageSelector->setLabelText(xi18nc("Page selector label", "Page:")); m_pageSelector->setButtonToolTipText(KexiRecordNavigator::ButtonFirst, xi18n("Go to first page")); m_pageSelector->setButtonWhatsThisText(KexiRecordNavigator::ButtonFirst, xi18n("Goes to first page")); m_pageSelector->setButtonToolTipText(KexiRecordNavigator::ButtonPrevious, xi18n("Go to previous page")); m_pageSelector->setButtonWhatsThisText(KexiRecordNavigator::ButtonPrevious, xi18n("Goes to previous page")); m_pageSelector->setButtonToolTipText(KexiRecordNavigator::ButtonNext, xi18n("Go to next page")); m_pageSelector->setButtonWhatsThisText(KexiRecordNavigator::ButtonNext, xi18n("Goes to next page")); m_pageSelector->setButtonToolTipText(KexiRecordNavigator::ButtonLast, xi18n("Go to last page")); m_pageSelector->setButtonWhatsThisText(KexiRecordNavigator::ButtonLast, xi18n("Goes to last page")); m_pageSelector->setNumberFieldToolTips(xi18n("Current page number"), xi18n("Number of pages")); m_pageSelector->setRecordHandler(this); #endif // -- setup local actions QList viewActions; QAction* a; #ifndef KEXI_MOBILE viewActions << (a = new QAction(koIcon("document-print"), xi18n("Print"), this)); a->setObjectName("print_report"); a->setToolTip(xi18n("Print report")); a->setWhatsThis(xi18n("Prints the current report.")); connect(a, SIGNAL(triggered()), this, SLOT(slotPrintReport())); KActionMenu *exportMenu = new KActionMenu(koIcon("document-export"), xi18nc("@title:menu","E&xport As"), this); exportMenu->setObjectName("report_export_as"); exportMenu->setDelayed(false); #endif #ifdef KEXI_SHOW_UNFINISHED #ifdef KEXI_MOBILE viewActions << (a = new QAction(xi18n("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 QAction(koIcon("application-vnd.oasis.opendocument.text"), QLatin1String(" "), this)); #else exportMenu->addAction(a = new QAction(koIcon("application-vnd.oasis.opendocument.text"), xi18nc("open dialog to export as text document", "Text Document..."), this)); #endif a->setObjectName("export_as_text_document"); a->setToolTip(xi18n("Export the report as a text document (in OpenDocument Text format)")); a->setWhatsThis(xi18n("Exports the report as a text document (in OpenDocument Text format).")); a->setEnabled(true); connect(a, SIGNAL(triggered()), this, SLOT(slotExportAsTextDocument())); #endif #ifdef KEXI_MOBILE viewActions << (a = new QAction(koIcon("application-pdf"), QLatin1String(" "), this)); #else exportMenu->addAction(a = new QAction(koIcon("application-pdf"), xi18nc("Portable Document Format...", "PDF..."), this)); #endif a->setObjectName("export_as_pdf"); a->setToolTip(xi18n("Export as PDF")); a->setWhatsThis(xi18n("Exports the current report as PDF.")); a->setEnabled(true); connect(a, SIGNAL(triggered()), this, SLOT(slotExportAsPdf())); #ifdef KEXI_SHOW_UNFINISHED #ifdef KEXI_MOBILE viewActions << (a = new QAction(koIcon("application-vnd.oasis.opendocument.spreadsheet"), QLatin1String(" "), this)); #else exportMenu->addAction(a = new QAction(koIcon("application-vnd.oasis.opendocument.spreadsheet"), xi18nc("open dialog to export as spreadsheet", "Spreadsheet..."), this)); #endif a->setObjectName("export_as_spreadsheet"); a->setToolTip(xi18n("Export the report as a spreadsheet (in OpenDocument Spreadsheet format)")); a->setWhatsThis(xi18n("Exports the report as a spreadsheet (in OpenDocument Spreadsheet format).")); a->setEnabled(true); connect(a, SIGNAL(triggered()), this, SLOT(slotExportAsSpreadsheet())); #endif #ifdef KEXI_MOBILE viewActions << (a = new QAction(koIcon("text-html"), QLatin1String(" "), this)); #else exportMenu->addAction(a = new QAction(koIcon("text-html"), xi18nc("open dialog to export as web page", "Web Page..."), this)); #endif a->setObjectName("export_as_web_page"); a->setToolTip(xi18n("Export the report as a web page (in HTML format)")); a->setWhatsThis(xi18n("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); #endif } KexiReportView::~KexiReportView() { delete m_preRenderer; } void KexiReportView::slotPrintReport() { QScopedPointer renderer(m_factory.createInstance("print")); if (!renderer) { return; } QPrinter printer(QPrinter::HighResolution); QPrintDialog dialog(&printer, this); if (dialog.exec() == QDialog::Accepted) { KReportRendererContext cxt; QPainter painter; cxt.setPrinter(&printer); cxt.setPainter(&painter); if (!renderer->render(cxt, m_preRenderer->document())) { KMessageBox::error(this, xi18n("Printing the report failed."), xi18n("Print Failed")); } } } void KexiReportView::slotExportAsPdf() { QScopedPointer renderer(m_factory.createInstance("print")); if (renderer) { KReportRendererContext cxt; cxt.setUrl(getExportUrl(QLatin1String("application/pdf"), xi18n("Export Report as PDF"), "kfiledialog:///LastVisitedPDFExportPath/", "pdf")); if (!cxt.url().isValid()) { return; } QPrinter printer; QPainter painter; printer.setOutputFileName(cxt.url().path()); printer.setOutputFormat(QPrinter::PdfFormat); printer.setColorMode(QPrinter::Color); painter.begin(&printer); cxt.setPrinter(&printer); cxt.setPainter(&painter); if (!renderer->render(cxt, m_preRenderer->document())) { KMessageBox::error(this, xi18n("Exporting the report as PDF to %1 failed.", cxt.url().toDisplayString()), xi18n("Export Failed")); } else { openExportedDocument(cxt.url()); } } } QUrl KexiReportView::getExportUrl(const QString &mimetype, const QString &caption, const QString &lastExportPath, const QString &extension) { QString defaultSavePath; QString recentDirClass; //TODO use utils defaultSavePath = KFileWidget::getStartUrl(QUrl(lastExportPath), recentDirClass).toLocalFile() + '/' + window()->partItem()->captionOrName() + '.' + extension; // loop until an url has been chosen or the file selection has been cancelled const QMimeDatabase db; const QString filterString = db.mimeTypeForName(mimetype).filterString(); return QFileDialog::getSaveFileUrl(this, caption, QUrl(defaultSavePath), filterString); } void KexiReportView::openExportedDocument(const QUrl &destination) { const int answer = KMessageBox::questionYesNo( this, xi18n("Do you want to open exported document?"), QString(), KStandardGuiItem::open(), KStandardGuiItem::close()); if (answer == KMessageBox::Yes) { (void)new KRun(destination, this->topLevelWidget()); } } #ifdef KEXI_SHOW_UNFINISHED void KexiReportView::slotExportAsSpreadsheet() { QScopedPointer renderer(m_factory.createInstance("ods")); if (renderer) { KReportRendererContext cxt; cxt.setUrl(getExportUrl(QLatin1String("application/vnd.oasis.opendocument.spreadsheet"), xi18n("Export Report as Spreadsheet"), "kfiledialog:///LastVisitedODSExportPath/", "ods")); if (!cxt.url().isValid()) { return; } if (!renderer->render(cxt, m_preRenderer->document())) { KMessageBox::error(this, xi18n("Failed to export the report as spreadsheet to %1.", cxt.url().toDisplayString()), xi18n("Export Failed")); } else { openExportedDocument(cxt.url()); } } } void KexiReportView::slotExportAsTextDocument() { QScopedPointer renderer(m_factory.createInstance("odt")); //! @todo Show error or don't show the commands to the user if the plugin isn't available. //! The same for other createInstance() calls. if (renderer) { KReportRendererContext cxt; cxt.setUrl(getExportUrl(QLatin1String("application/vnd.oasis.opendocument.text"), xi18n("Export Report as Text Document"), "kfiledialog:///LastVisitedODTExportPath/", "odt")); if (!cxt.url().isValid()) { return; } if (!renderer->render(cxt, m_preRenderer->document())) { KMessageBox::error(this, xi18n("Exporting the report as text document to %1 failed.", cxt.url().toDisplayString()), xi18n("Export Failed")); } else { openExportedDocument(cxt.url()); } } } #endif void KexiReportView::slotExportAsWebPage() { const QString dialogTitle = xi18n("Export Report as Web Page"); KReportRendererContext cxt; cxt.setUrl(getExportUrl(QLatin1String("text/html"), dialogTitle, "kfiledialog:///LastVisitedHTMLExportPath/", "html")); if (!cxt.url().isValid()) { return; } const int answer = KMessageBox::questionYesNo( this, xi18nc("@info", "Would you like to use Cascading Style Sheets (CSS) in the exported " "web page or use HTML tables?" "CSS give output closer to the original."), dialogTitle, KGuiItem(xi18nc("@action:button", "Use CSS")), KGuiItem(xi18nc("@action:button", "Use Table"))); QScopedPointer renderer( m_factory.createInstance(answer == KMessageBox::Yes ? "htmlcss" : "htmltable")); if (!renderer) { return; } if (!renderer->render(cxt, m_preRenderer->document())) { KMessageBox::error(this, xi18n("Exporting the report as web page to %1 failed.", cxt.url().toDisplayString()), xi18n("Export Failed")); } else { openExportedDocument(cxt.url()); } } 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; delete m_preRenderer; //qDebug() << tempData()->reportDefinition.tagName(); m_preRenderer = new KReportPreRenderer(tempData()->reportDefinition); if (m_preRenderer->isValid()) { KReportDataSource *reportData = 0; if (!tempData()->connectionDefinition.isNull()) { reportData = createDataSource(tempData()->connectionDefinition); } m_preRenderer->setDataSource(reportData); m_preRenderer->setScriptSource(qobject_cast(part())); m_preRenderer->setName(window()->partItem()->name()); //Add a kexi object to provide kexidb and extra functionality //! @todo KEXI3 if we want this 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") { m_functions = new KRScriptFunctions(reportData, KexiMainWindowIface::global()->project()->dbConnection()); m_preRenderer->registerScriptObject(m_functions, "field"); connect(m_preRenderer, SIGNAL(groupChanged(QMap)), m_functions, SLOT(setGroupData(QMap))); } connect(m_preRenderer, SIGNAL(finishedAllASyncItems()), this, SLOT(finishedAllASyncItems())); if (!m_preRenderer->generateDocument()) { qWarning() << "Could not generate report document"; return false; } m_reportView->setDocument(m_preRenderer->document()); #ifndef KEXI_MOBILE m_pageSelector->setRecordCount(m_reportView->pageCount()); m_pageSelector->setCurrentRecordNumber(1); #endif } else { KMessageBox::error(this, xi18n("Report schema appears to be invalid or corrupt"), xi18n("Opening failed")); } } return true; } KReportDataSource* KexiReportView::createDataSource(const QDomElement &e) { - KReportDataSource *kodata = 0; - if (e.attribute("type") == "internal" && !e.attribute("source").isEmpty()) { - kodata - = new KexiDBReportDataSource(e.attribute("source"), e.attribute("class"), - KexiMainWindowIface::global()->project()->dbConnection()); - } -#ifndef KEXI_MOBILE -//! @todo KEXI3 -#if 0 - if (e.attribute("type") == "external") { - kodata = new KexiMigrateReportData(e.attribute("source")); + return new KexiDBReportDataSource(e.attribute("source"), e.attribute("class"), tempData()); } -#endif -#endif - return kodata; + return nullptr; } KexiReportPartTempData* KexiReportView::tempData() const { return static_cast(window()->data()); } void KexiReportView::addNewRecordRequested() { } void KexiReportView::moveToFirstRecordRequested() { m_reportView->moveToFirstPage(); #ifndef KEXI_MOBILE m_pageSelector->setCurrentRecordNumber(m_reportView->currentPage()); #endif } void KexiReportView::moveToLastRecordRequested() { m_reportView->moveToLastPage(); #ifndef KEXI_MOBILE m_pageSelector->setCurrentRecordNumber(m_reportView->currentPage()); #endif } void KexiReportView::moveToNextRecordRequested() { m_reportView->moveToNextPage(); #ifndef KEXI_MOBILE m_pageSelector->setCurrentRecordNumber(m_reportView->currentPage()); #endif } void KexiReportView::moveToPreviousRecordRequested() { m_reportView->moveToPreviousPage(); #ifndef KEXI_MOBILE m_pageSelector->setCurrentRecordNumber(m_reportView->currentPage()); #endif } void KexiReportView::moveToRecordRequested(int r) { #ifdef KEXI_MOBILE m_reportView->moveToPage(r + 1); #else // set in the navigator widget first, this will fix up the value it it's too small or large m_pageSelector->setCurrentRecordNumber(r + 1); m_reportView->moveToPage(m_pageSelector->currentRecordNumber()); #endif } int KexiReportView::currentRecord() const { return m_reportView->currentPage(); } int KexiReportView::recordCount() const { return m_reportView->pageCount(); } void KexiReportView::finishedAllASyncItems() { m_reportView->refreshCurrentPage(); } diff --git a/src/plugins/reports/kexisourceselector.cpp b/src/plugins/reports/kexisourceselector.cpp index 4ac46e29f..cd3c645c6 100644 --- a/src/plugins/reports/kexisourceselector.cpp +++ b/src/plugins/reports/kexisourceselector.cpp @@ -1,105 +1,90 @@ /* * Kexi Report Plugin * Copyright (C) 2007-2009 by Adam Pigg * Copyright (C) 2017 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 "kexisourceselector.h" #include "KexiDataSourceComboBox.h" #include -#include #include #include #include #include #include class Q_DECL_HIDDEN KexiSourceSelector::Private { public: Private() { } ~Private() { } KDbConnection *conn; QVBoxLayout *layout; KexiDataSourceComboBox *dataSource; }; KexiSourceSelector::KexiSourceSelector(KexiProject* project, QWidget* parent) : QWidget(parent) , d(new Private) { d->conn = project->dbConnection(); d->layout = new QVBoxLayout(this); d->dataSource = new KexiDataSourceComboBox(this); d->dataSource->setProject(project); connect(d->dataSource, &KexiDataSourceComboBox::dataSourceChanged, this, &KexiSourceSelector::dataSourceChanged); QLabel *label = new QLabel(xi18n("Report's data source:")); label->setBuddy(d->dataSource); d->layout->addWidget(label); d->layout->addWidget(d->dataSource); d->layout->addStretch(); setLayout(d->layout); } KexiSourceSelector::~KexiSourceSelector() { delete d; } -void KexiSourceSelector::setConnectionData(const QDomElement &c) +QString KexiSourceSelector::selectedPluginId() const { - qDebug() << c; - if (c.attribute("type") == "internal") { - QString sourceClass(c.attribute("class")); - if (sourceClass != "org.kexi-project.table" && sourceClass != "org.kexi-project.query") { - sourceClass.clear(); // KexiDataSourceComboBox will try to find table, then query - } - d->dataSource->setDataSource(sourceClass, c.attribute("source")); - emit dataSourceChanged(); - } + return d->dataSource->selectedPluginId(); } -QDomElement KexiSourceSelector::connectionData() +QString KexiSourceSelector::selectedName() const { - QDomDocument dd; - QDomElement conndata = dd.createElement("connection"); - conndata.setAttribute("type", "internal"); // for backward compatibility, currently always - // internal, we used to have "external" in old Kexi - conndata.setAttribute("source", d->dataSource->selectedName()); - conndata.setAttribute("class", d->dataSource->selectedPluginId()); - return conndata; + return d->dataSource->selectedName(); } -KReportDataSource* KexiSourceSelector::createDataSource() const +bool KexiSourceSelector::isSelectionValid() const { - if (d->dataSource->isSelectionValid()) { - return new KexiDBReportDataSource(d->dataSource->selectedName(), - d->dataSource->selectedPluginId(), d->conn); - } - return nullptr; + return d->dataSource->isSelectionValid(); } +void KexiSourceSelector::setDataSource(const QString& pluginId, const QString& name) +{ + d->dataSource->setDataSource(pluginId, name); +} diff --git a/src/plugins/reports/kexisourceselector.h b/src/plugins/reports/kexisourceselector.h index 6dc1aa186..3aa0d27ea 100644 --- a/src/plugins/reports/kexisourceselector.h +++ b/src/plugins/reports/kexisourceselector.h @@ -1,56 +1,70 @@ /* * Kexi Report Plugin * Copyright (C) 2007-2016 by Adam Pigg * Copyright (C) 2017 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 KEXISOURCESELECTOR_H #define KEXISOURCESELECTOR_H #include #include #include "KexiDBReportDataSource.h" #ifdef HAVE_KEXI_MIGRATE #include "keximigratereportdata.h" #endif class QDomElement; class KexiProject; //! @todo rename to KexiReportDataSourcePage //! @todo use KexiPropertyPaneViewBase class KexiSourceSelector : public QWidget { Q_OBJECT public: explicit KexiSourceSelector(KexiProject* project, QWidget* parent = 0); ~KexiSourceSelector(); KReportDataSource* createDataSource() const Q_REQUIRED_RESULT; - void setConnectionData(const QDomElement &c); + QDomElement connectionData(); + //! @return name plugin ID of selected item (a table or a query). Can return an empty string. + QString selectedPluginId() const; + + //! @return name of selected table or query. + QString selectedName() const; + + //! \return true if the current selection is valid + bool isSelectionValid() const; + +public Q_SLOTS: + /*! Sets item for data source described by \a pluginId and \a name. + If \a pluginId is empty, either "org.kexi-project.table" and "org.kexi-project.query" are tried. */ + void setDataSource(const QString& pluginId, const QString& name); + Q_SIGNALS: void dataSourceChanged(); private: class Private; Private * const d; }; #endif // KEXISOURCESELECTOR_H diff --git a/src/plugins/tables/kexitabledesignerview.cpp b/src/plugins/tables/kexitabledesignerview.cpp index 7debc020d..a24ad729d 100644 --- a/src/plugins/tables/kexitabledesignerview.cpp +++ b/src/plugins/tables/kexitabledesignerview.cpp @@ -1,1896 +1,1901 @@ /* This file is part of the KDE project - Copyright (C) 2004-2012 Jarosław Staniek + Copyright (C) 2004-2017 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 "kexitabledesignerview.h" #include "kexitabledesignerview_p.h" #include "kexilookupcolumnpage.h" #include "kexitabledesignercommands.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 //! used only for BLOBs #define DEFAULT_OBJECT_TYPE_VALUE "image" //#define KexiTableDesignerView_DEBUG //! @todo remove this when BLOBs are implemented //#define KEXI_NO_BLOB_FIELDS // Defining this removes alter table! #define KEXI_NO_UNDOREDO_ALTERTABLE using namespace KexiTableDesignerCommands; //! @internal Used in tryCastQVariant() anf canCastQVariant() static bool isIntegerQVariant(QVariant::Type t) { return t == QVariant::LongLong || t == QVariant::ULongLong || t == QVariant::Int || t == QVariant::UInt; } //! @internal Used in tryCastQVariant() static bool canCastQVariant(QVariant::Type fromType, QVariant::Type toType) { return (fromType == QVariant::Int && toType == QVariant::UInt) || (fromType == QVariant::ByteArray && toType == QVariant::String) || (fromType == QVariant::LongLong && toType == QVariant::ULongLong) || ((fromType == QVariant::String || fromType == QVariant::ByteArray) && (isIntegerQVariant(toType) || toType == QVariant::Double)); } /*! @internal \return a variant value converted from \a fromVal to \a toType type. Null QVariant is returned if \a fromVal's type and \a toType type are incompatible. */ static QVariant tryCastQVariant(const QVariant& fromVal, QVariant::Type toType) { const QVariant::Type fromType = fromVal.type(); if (fromType == toType) return fromVal; if (canCastQVariant(fromType, toType) || canCastQVariant(toType, fromType) || (isIntegerQVariant(fromType) && toType == QVariant::Double)) { QVariant res(fromVal); if (res.convert(toType)) return res; } return QVariant(); } KexiTableDesignerView::KexiTableDesignerView(QWidget *parent) : KexiDataTableView(parent, false/*not db-aware*/) , KexiTableDesignerInterface() , d(new KexiTableDesignerViewPrivate(this)) { setObjectName("KexiTableDesignerView"); KDbConnection *conn = KexiMainWindowIface::global()->project()->dbConnection(); d->view = dynamic_cast(mainWidget()); d->data = new KDbTableViewData(); if (conn->options()->isReadOnly()) d->data->setReadOnly(true); d->data->setInsertingEnabled(false); KDbTableViewColumn *col = new KDbTableViewColumn("pk", KDbField::Text, QString(), xi18n("Additional information about the field")); col->setIcon(KexiUtils::colorizeIconToTextColor(koSmallIcon("help-about"), d->view->palette())); col->setHeaderTextVisible(false); col->field()->setSubType("QIcon"); col->setReadOnly(true); d->data->addColumn(col); col = new KDbTableViewColumn("caption", KDbField::Text, xi18n("Field Caption"), xi18n("Describes caption for the field")); d->data->addColumn(col); col = new KDbTableViewColumn("type", KDbField::Enum, xi18n("Data Type"), xi18n("Describes data type for the field")); d->data->addColumn(col); #ifdef KEXI_NO_BLOB_FIELDS //! @todo remove this later QVector types(KDbField::LastTypeGroup - 1); //don't show last type (BLOB) #else QVector types(KDbField::LastTypeGroup); #endif d->maxTypeNameTextWidth = 0; QFontMetrics fm(font()); for (int i = KDbField::TextGroup; i <= types.count(); i++) { types[i-1] = KDbField::typeGroupName(KDb::intToFieldTypeGroup(i)); d->maxTypeNameTextWidth = qMax(d->maxTypeNameTextWidth, fm.width(types[i-1])); } col->field()->setEnumHints(types); d->data->addColumn(col = new KDbTableViewColumn("comments", KDbField::Text, xi18n("Comments"), xi18n("Describes additional comments for the field"))); d->view->setSpreadSheetMode(true); connect(d->data, SIGNAL(aboutToChangeCell(KDbRecordData*,int,QVariant*,KDbResultInfo*)), this, SLOT(slotBeforeCellChanged(KDbRecordData*,int,QVariant*,KDbResultInfo*))); connect(d->data, SIGNAL(recordUpdated(KDbRecordData*)), this, SLOT(slotRecordUpdated(KDbRecordData*))); connect(d->data, SIGNAL(aboutToDeleteRecord(KDbRecordData*,KDbResultInfo*,bool)), this, SLOT(slotAboutToDeleteRecord(KDbRecordData*,KDbResultInfo*,bool))); setMinimumSize(d->view->minimumSizeHint().width(), d->view->minimumSizeHint().height()); d->view->setFocus(); d->sets = new KexiDataAwarePropertySet(this, d->view); connect(d->sets, SIGNAL(recordDeleted()), this, SLOT(updateActions())); connect(d->sets, SIGNAL(recordInserted()), this, SLOT(slotRecordInserted())); connect(d->view->contextMenu(), SIGNAL(aboutToShow()), this, SLOT(slotAboutToShowContextMenu())); // - setup local actions QList viewActions; QAction* a; viewActions << (d->action_toggle_pkey = new KToggleAction(KexiIcon("database-key"), xi18n("Primary Key"), this)); a = d->action_toggle_pkey; a->setObjectName("tablepart_toggle_pkey"); a->setToolTip(xi18n("Sets or deletes primary key")); a->setWhatsThis(xi18n("Sets or deletes primary key for currently selected field.")); connect(a, SIGNAL(triggered()), this, SLOT(slotTogglePrimaryKey())); setViewActions(viewActions); d->view->contextMenu()->insertAction( d->view->contextMenu()->actions()[1], d->action_toggle_pkey); //add at the beginning as 2nd d->view->contextMenu()->insertSeparator(d->view->contextMenu()->actions()[2]); //as 3rd setAvailable("tablepart_toggle_pkey", !conn->options()->isReadOnly()); #ifndef KEXI_NO_UNDOREDO_ALTERTABLE plugSharedAction("edit_undo", this, SLOT(slotUndo())); plugSharedAction("edit_redo", this, SLOT(slotRedo())); setAvailable("edit_undo", false); setAvailable("edit_redo", false); #endif #ifdef KEXI_DEBUG_GUI KDb::alterTableActionDebugGUI(QString()); //to create the tab KexiUtils::connectPushButtonActionForDebugWindow( "simulateAlterTableExecution", this, SLOT(slotSimulateAlterTableExecution())); KexiUtils::connectPushButtonActionForDebugWindow( "executeRealAlterTable", this, SLOT(executeRealAlterTable())); #endif // setup main menu actions QList mainMenuActions; a = sharedAction("edit_clear_table"); mainMenuActions << a; setMainMenuActions(mainMenuActions); } KexiTableDesignerView::~KexiTableDesignerView() { delete d; } void KexiTableDesignerView::initData() { //add column data d->data->deleteAllRecords(); int tableFieldCount = 0; d->primaryKeyExists = false; if (tempData()->table()) { tableFieldCount = tempData()->table()->fieldCount(); //recreate table data records for (int i = 0; i < tableFieldCount; i++) { KDbField *field = tempData()->table()->field(i); KDbRecordData *data = d->data->createItem(); if (field->isPrimaryKey()) { (*data)[COLUMN_ID_ICON] = KexiIconName("database-key"); d->primaryKeyExists = true; } else { KDbLookupFieldSchema *lookupFieldSchema = field->table() ? field->table()->lookupFieldSchema(*field) : 0; if (lookupFieldSchema && lookupFieldSchema->recordSource().type() != KDbLookupFieldSchemaRecordSource::Type::None && !lookupFieldSchema->recordSource().name().isEmpty()) { (*data)[COLUMN_ID_ICON] = KexiIconName("combobox"); } } (*data)[COLUMN_ID_CAPTION] = field->captionOrName(); (*data)[COLUMN_ID_TYPE] = field->typeGroup() - 1; //-1 because type groups are counted from 1 (*data)[COLUMN_ID_DESC] = field->description(); d->data->append(data); } } //add empty space, at least 2 times more than number of existing fields int fullSize = qMax(d->sets->size(), 2 * tableFieldCount); for (int i = tableFieldCount; i < fullSize; i++) { d->data->append(d->data->createItem()); } //set data for our spreadsheet: this will clear our sets d->view->setData(d->data); //now recreate property sets if (tempData()->table()) { for (int i = 0; i < tableFieldCount; i++) { KDbField *field = tempData()->table()->field(i); createPropertySet(i, *field); } } //column widths d->view->setColumnWidth(COLUMN_ID_ICON, IconSize(KIconLoader::Small) + 10); d->view->setColumnResizeEnabled(COLUMN_ID_ICON, false); d->view->adjustColumnWidthToContents(COLUMN_ID_CAPTION); //adjust column width d->view->setColumnWidth(COLUMN_ID_TYPE, d->maxTypeNameTextWidth + 2 * d->view->recordHeight()); d->view->setStretchLastColumn(true); const int minCaptionColumnWidth = d->view->fontMetrics().width("wwwwwwwwwww"); if (minCaptionColumnWidth > d->view->columnWidth(COLUMN_ID_CAPTION)) d->view->setColumnWidth(COLUMN_ID_CAPTION, minCaptionColumnWidth); setDirty(false); d->view->setCursorPosition(0, COLUMN_ID_CAPTION); //set @ name column propertySetSwitched(); } //! Gets subtype strings and names for type \a fieldType static KPropertyListData *getSubTypeListData(KDbField::TypeGroup fieldTypeGroup) { /* disabled - "mime" is moved from subType to "objectType" custom property if (fieldTypeGroup==KDbField::BLOBGroup) { // special case: BLOB type uses "mime-based" subtypes //! @todo hardcoded! stringsList << "image"; namesList << xi18n("Image object type", "Image"); } else {*/ KPropertyListData *listData = new KPropertyListData( KDb::fieldTypeStringsForGroup(fieldTypeGroup), KDb::fieldTypeNamesForGroup(fieldTypeGroup)); // } qDebug() << "subType strings: " << listData->keysAsStringList().join("|") << "\nnames: " << listData->namesAsStringList().join("|"); return listData; } KPropertySet * KexiTableDesignerView::createPropertySet(int record, const KDbField& field, bool newOne) { KPropertySet *set = new KPropertySet(d->sets); if (KexiMainWindowIface::global()->project()->dbConnection()->options()->isReadOnly()) set->setReadOnly(true); KProperty *prop; set->addProperty(prop = new KProperty("uid", d->generateUniqueId(), "")); prop->setVisible(false); //meta-info for property editor set->addProperty(prop = new KProperty("this:classString", xi18nc("Table field", "Field"))); prop->setVisible(false); set->addProperty(prop = new KProperty("this:iconName", //! \todo add table_field icon KexiIconName("lineedit") //"table_field" )); prop->setVisible(false); set->addProperty(prop = new KProperty("this:useCaptionAsObjectName", QVariant(true), QString())); //we want "caption" to be displayed in the header, not name prop->setVisible(false); //name set->addProperty(prop = new KProperty( "name", QVariant(field.name()), xi18n("Name"), QString(), KexiCustomPropertyFactory::Identifier)); //type set->addProperty(prop = new KProperty("type", QVariant(field.type()), xi18n("Type"))); #ifndef KexiTableDesignerView_DEBUG prop->setVisible(false);//always hidden #endif //subtype KPropertyListData *listData = getSubTypeListData(field.typeGroup()); /* disabled - "mime" is moved from subType to "objectType" custom property QString subTypeValue; if (field.typeGroup()==KDbField::BLOBGroup) { // special case: BLOB type uses "mime-based" subtypes //! @todo this should be retrieved from KDbField when BLOB supports many different mimetypes subTypeValue = slist.first(); } else {*/ QVariant subTypeValue = field.typeString(); //} set->addProperty(prop = new KProperty("subType", listData, subTypeValue, xi18n("Subtype"))); // objectType //! @todo this should be retrieved from KDbField when BLOB supports many different mimetypes listData = new KPropertyListData({ "image" }, QVariantList{ xi18nc("Image object type", "Image") }); QVariant objectTypeValue(field.customProperty("objectType")); if (objectTypeValue.toString().isEmpty()) objectTypeValue = DEFAULT_OBJECT_TYPE_VALUE; set->addProperty(prop = new KProperty("objectType", listData, objectTypeValue, xi18n("Subtype")/*! @todo other i18n string?*/)); set->addProperty(prop = new KProperty("caption", QVariant(field.caption()), xi18n("Caption"))); prop->setVisible(false);//always hidden set->addProperty(prop = new KProperty("description", QVariant(field.description()))); prop->setVisible(false);//always hidden set->addProperty(prop = new KProperty("unsigned", QVariant(field.isUnsigned()), xi18n("Unsigned Number"))); set->addProperty(prop = new KProperty("maxLength", field.maxLength(), xi18n("Max Length"))); set->addProperty(prop = new KProperty("maxLengthIsDefault", field.maxLengthStrategy() == KDbField::DefaultMaxLength)); prop->setVisible(false); //always hidden set->addProperty(prop = new KProperty("precision", (int)field.precision()/*200?*/, xi18n("Precision"))); #ifndef KEXI_SHOW_UNFINISHED prop->setVisible(false); #endif set->addProperty(prop = new KProperty("visibleDecimalPlaces", field.visibleDecimalPlaces(), xi18n("Visible Decimal Places"))); prop->setOption("min", -1); prop->setOption("minValueText", xi18nc("Auto Decimal Places", "Auto")); //! @todo set reasonable default for column width set->addProperty(prop = new KProperty("defaultWidth", QVariant(0) /*field.width()*//*200?*/, xi18n("Default Width"))); #ifndef KEXI_SHOW_UNFINISHED prop->setVisible(false); #endif set->addProperty(prop = new KProperty("defaultValue", field.defaultValue(), xi18n("Default Value"), QString(), //! @todo use "Variant" type here when supported by KProperty (KProperty::Type)field.variantType())); prop->setOption("3rdState", xi18n("None")); set->addProperty(prop = new KProperty("primaryKey", QVariant(field.isPrimaryKey()), xi18n("Primary Key"))); prop->setIconName(KexiIconName("database-key")); set->addProperty(prop = new KProperty("unique", QVariant(field.isUniqueKey()), xi18n("Unique"))); set->addProperty(prop = new KProperty("notNull", QVariant(field.isNotNull()), xi18n("Required"))); set->addProperty(prop = new KProperty("allowEmpty", QVariant(!field.isNotEmpty()), xi18n("Allow Zero\nSize"))); set->addProperty(prop = new KProperty("autoIncrement", QVariant(field.isAutoIncrement()), xi18n("Autonumber"))); prop->setIconName(koIconName("autonumber")); set->addProperty(prop = new KProperty("indexed", QVariant(field.isIndexed()), xi18n("Indexed"))); //- properties related to lookup columns (used and set by the "lookup column" // tab in the property pane) const KDbLookupFieldSchema *lookupFieldSchema = field.table() ? field.table()->lookupFieldSchema(field) : nullptr; set->addProperty(prop = new KProperty("rowSource", lookupFieldSchema ? lookupFieldSchema->recordSource().name() : QString(), xi18n("Record Source"))); prop->setVisible(false); set->addProperty(prop = new KProperty("rowSourceType", lookupFieldSchema ? lookupFieldSchema->recordSource().typeName() : QString(), xi18nc("Record source type (in two records)", "Record Source\nType"))); prop->setVisible(false); set->addProperty(prop = new KProperty("boundColumn", lookupFieldSchema ? lookupFieldSchema->boundColumn() : -1, xi18n("Bound Column"))); prop->setVisible(false); //! @todo this is backward-compatible code for "single visible column" implementation //! for multiple columns, only the first is displayed, so there is a data loss is GUI is used //! -- special kproperty editor needed int visibleColumn = -1; if (lookupFieldSchema && !lookupFieldSchema->visibleColumns().isEmpty()) visibleColumn = lookupFieldSchema->visibleColumns().first(); set->addProperty(prop = new KProperty("visibleColumn", visibleColumn, xi18n("Visible Column"))); prop->setVisible(false); //! @todo support columnWidths(), columnHeadersVisible(), maxVisibleRecords(), limitToList(), displayWidget() //---- d->updatePropertiesVisibility(field.type(), *set); connect(set, SIGNAL(propertyChanged(KPropertySet&,KProperty&)), this, SLOT(slotPropertyChanged(KPropertySet&,KProperty&))); d->sets->set(record, set, newOne); return set; } void KexiTableDesignerView::updateActions(bool activated) { Q_UNUSED(activated); /*! \todo check if we can set pkey for this column type (eg. BLOB?) */ setAvailable("tablepart_toggle_pkey", propertySet() != 0 && !KexiMainWindowIface::global()->project()->dbConnection()->options()->isReadOnly()); if (!propertySet()) return; KPropertySet &set = *propertySet(); d->slotTogglePrimaryKeyCalled = true; d->action_toggle_pkey->setChecked(set["primaryKey"].value().toBool()); d->slotTogglePrimaryKeyCalled = false; } void KexiTableDesignerView::slotUpdateRecordActions(int record) { KexiDataTableView::slotUpdateRecordActions(record); updateActions(); } void KexiTableDesignerView::slotTogglePrimaryKey() { if (d->slotTogglePrimaryKeyCalled) return; d->slotTogglePrimaryKeyCalled = true; if (!propertySet()) return; KPropertySet &set = *propertySet(); bool isSet = !set["primaryKey"].value().toBool(); set.changeProperty("primaryKey", QVariant(isSet)); //this will update all related properties as well d->slotTogglePrimaryKeyCalled = false; } void KexiTableDesignerView::switchPrimaryKey(KPropertySet &propertySet, bool set, bool aWasPKey, Command* commandGroup) { const bool was_pkey = aWasPKey || propertySet["primaryKey"].value().toBool(); d->setPropertyValueIfNeeded(propertySet, "primaryKey", QVariant(set), commandGroup); if (&propertySet == this->propertySet()) { //update action and icon @ column 0 (only if we're changing current property set) d->action_toggle_pkey->setChecked(set); if (d->view->selectedRecord()) { //show key in the table d->view->data()->clearRecordEditBuffer(); d->view->data()->updateRecordEditBuffer(d->view->selectedRecord(), COLUMN_ID_ICON, QVariant(set ? KexiIconName("database-key") : QLatin1String(""))); d->view->data()->saveRecordChanges(d->view->selectedRecord(), true); } if (was_pkey || set) //change flag only if we're setting pk or really clearing it d->primaryKeyExists = set; } if (set) { //primary key is set, remove old pkey if exists KPropertySet *s = 0; int i; const int count = (int)d->sets->size(); for (i = 0; i < count; i++) { s = d->sets->at(i); if ( s && s != &propertySet && (*s)["primaryKey"].value().toBool() && i != d->view->currentRecord()) { break; } } if (i < count) {//remove d->setPropertyValueIfNeeded(*s, "autoIncrement", QVariant(false), commandGroup); d->setPropertyValueIfNeeded(*s, "primaryKey", QVariant(false), commandGroup); //remove key from table d->view->data()->clearRecordEditBuffer(); KDbRecordData *data = d->view->recordAt(i); if (data) { d->view->data()->updateRecordEditBuffer(data, COLUMN_ID_ICON, QVariant()); d->view->data()->saveRecordChanges(data, true); } } //set unsigned big-integer type d->slotBeforeCellChanged_enabled = false; d->view->data()->clearRecordEditBuffer(); d->view->data()->updateRecordEditBuffer(d->view->selectedRecord(), COLUMN_ID_TYPE, QVariant(KDbField::IntegerGroup - 1/*counting from 0*/)); d->view->data()->saveRecordChanges(d->view->selectedRecord(), true); d->setPropertyValueIfNeeded(propertySet, "subType", KDbField::typeString(KDbField::BigInteger), commandGroup); d->setPropertyValueIfNeeded(propertySet, "unsigned", QVariant(true), commandGroup); //! @todo d->slotBeforeCellChanged_enabled = true; } updateActions(); } tristate KexiTableDesignerView::beforeSwitchTo(Kexi::ViewMode mode, bool *dontStore) { Q_ASSERT(dontStore); if (!d->view->acceptRecordEditing()) return false; tristate res = true; if (mode == Kexi::DataViewMode) { if (!isDirty() && window()->neverSaved()) { KMessageBox::sorry(this, xi18n("Cannot switch to data view, because table design is empty.\n" "First, please create your design.")); return cancelled; } // else if (isDirty() && !window()->neverSaved()) { // cancelled = (KMessageBox::No == KMessageBox::questionYesNo(this, xi18n("Saving changes for existing table design is not yet supported.\nDo you want to discard your changes now?"))); // KDbConnection *conn = KexiMainWindowIface::global()->project()->dbConnection(); bool emptyTable; bool isPhysicalAlteringNeeded = this->isPhysicalAlteringNeeded(); KLocalizedString message( kxi18nc("@info", "Saving changes for existing table design is now required.%1") .subs(d->messageForSavingChanges(&emptyTable, /*skip warning?*/!isPhysicalAlteringNeeded))); if (emptyTable) { isPhysicalAlteringNeeded = false; // eventually, not needed because there's no data } KGuiItem saveItem(KStandardGuiItem::save()); saveItem.setToolTip(QString()); KGuiItem discardItem(KStandardGuiItem::discard()); discardItem.setToolTip(QString()); if (isPhysicalAlteringNeeded) { saveItem.setText(xi18nc("@action:button", "Save Design and Delete Table Data")); discardItem.setText(xi18nc("@action:button", "Discard Design")); } const KMessageBox::ButtonCode r = KMessageBox::warningYesNoCancel(this, message.toString(), QString(), saveItem, discardItem, KStandardGuiItem::cancel(), QString(), KMessageBox::Notify | KMessageBox::Dangerous); if (r == KMessageBox::Cancel) res = cancelled; else res = true; *dontStore = (r != KMessageBox::Yes); if (!*dontStore) d->dontAskOnStoreData = true; } // //! @todo return res; } else if (mode == Kexi::TextViewMode) { //! @todo } return res; } tristate KexiTableDesignerView::afterSwitchFrom(Kexi::ViewMode mode) { if (mode == Kexi::NoViewMode || mode == Kexi::DataViewMode) { initData(); } return true; } KPropertySet *KexiTableDesignerView::propertySet() { return d->sets ? d->sets->currentPropertySet() : 0; } void KexiTableDesignerView::slotBeforeCellChanged( KDbRecordData *data, int colnum, QVariant* newValue, KDbResultInfo* /*result*/) { if (!d->slotBeforeCellChanged_enabled) return; // qDebug() << d->view->selectedRecord() << " " << item //<< " " << d->sets->at( d->view->currentRecord() ) << " " << propertySet(); if (colnum == COLUMN_ID_CAPTION) {//'caption' //if 'type' is not filled yet if (data->at(COLUMN_ID_TYPE).isNull()) { //auto select 1st record of 'type' column d->view->data()->updateRecordEditBuffer(data, COLUMN_ID_TYPE, QVariant((int)0)); } KPropertySet *propertySetForRecord = d->sets->findPropertySetForItem(*data); if (propertySetForRecord) { d->addHistoryCommand_in_slotPropertyChanged_enabled = false; // because we'll add // the two changes as one group QString oldName(propertySetForRecord->property("name").value().toString()); QString oldCaption(propertySetForRecord->property("caption").value().toString()); //remember this action containing 2 subactions //Parent command is a Command containing 2 child commands Command *changeCaptionAndNameCommand = new Command( kundo2_i18n( "Change %1 field name to %2 " "and caption from %3 to %4", oldName, propertySetForRecord->property("name").value().toString(), oldCaption, newValue->toString()), 0, this ); //we need to create the action now as set["name"] will be changed soon. //Child 1 is the caption /*ChangeFieldPropertyCommand *changeCaptionCommand = */ (void)new ChangeFieldPropertyCommand(changeCaptionAndNameCommand, this, *propertySetForRecord, "caption", oldCaption, *newValue); //update field caption and name propertySetForRecord->changeProperty("caption", *newValue); propertySetForRecord->changeProperty("name", KDb::stringToIdentifier(newValue->toString())); //Child 2 is the name /*ChangeFieldPropertyCommand *changeNameCommand =*/ (void)new ChangeFieldPropertyCommand( changeCaptionAndNameCommand, this, *propertySetForRecord, "name", oldName, propertySetForRecord->property("name").value().toString()); addHistoryCommand(changeCaptionAndNameCommand, false /* !execute */); d->addHistoryCommand_in_slotPropertyChanged_enabled = true; } } else if (colnum == COLUMN_ID_TYPE) {//'type' if (newValue->isNull()) { //'type' col will be cleared: clear all other columns as well d->slotBeforeCellChanged_enabled = false; d->view->data()->updateRecordEditBuffer(data, COLUMN_ID_ICON, QVariant()); d->view->data()->updateRecordEditBuffer(data, COLUMN_ID_CAPTION, QVariant(QString())); d->view->data()->updateRecordEditBuffer(data, COLUMN_ID_DESC, QVariant()); d->slotBeforeCellChanged_enabled = true; return; } KPropertySet *propertySetForRecord = d->sets->findPropertySetForItem(*data); if (!propertySetForRecord) return; KPropertySet &set = *propertySetForRecord; //propertySet(); //'type' col is changed (existed before) //-get type group number KDbField::TypeGroup fieldTypeGroup; int i_fieldTypeGroup = newValue->toInt() + 1/*counting from 1*/; if (i_fieldTypeGroup < 1 || i_fieldTypeGroup > #ifdef KEXI_NO_BLOB_FIELDS //! @todo remove this later (int)KDbField::LastTypeGroup - 1) //don't show last (BLOB) type #else (int)KDbField::LastTypeGroup) #endif return; fieldTypeGroup = static_cast(i_fieldTypeGroup); //-get 1st type from this group, and update 'type' property KDbField::Type fieldType = KDb::defaultFieldTypeForGroup(fieldTypeGroup); if (fieldType == KDbField::InvalidType) fieldType = KDbField::Text; //-get subtypes for this type: KPropertyListData *listData = getSubTypeListData(fieldTypeGroup); QVariant subTypeValue; /* disabled - "mime" is moved from subType to "objectType" custom property if (fieldType==KDbField::BLOB) { // special case: BLOB type uses "mime-based" subtypes subTypeValue = listData->keys().first(); } else {*/ subTypeValue = KDbField::typeString(fieldType); //} KProperty *subTypeProperty = &set["subType"]; qDebug() << subTypeProperty->value(); // *** this action contains subactions *** Command *changeDataTypeCommand = new Command( kundo2_i18n("Change data type for field %1 to %2", set["name"].value().toString(), KDbField::typeName(fieldType)), 0, this); //qDebug() << "++++++++++" << slist << nlist; //update subtype list and value const bool forcePropertySetReload = KDbField::typeGroup( KDbField::typeForString(subTypeProperty->value().toString())) != fieldTypeGroup; //<-- ????? const bool useListData = listData->keys().count() > 1; if (!useListData) { *listData = KPropertyListData(); //empty list will be passed } d->setPropertyValueIfNeeded(set, "type", (int)fieldType, changeDataTypeCommand, false /*!forceAddCommand*/, true /*rememberOldValue*/); // notNull and defaultValue=false is reasonable for boolean type if (fieldType == KDbField::Boolean) { //! @todo maybe this is good for other data types as well? d->setPropertyValueIfNeeded(set, "notNull", QVariant(true), changeDataTypeCommand, false /*!forceAddCommand*/, false /*!rememberOldValue*/); d->setPropertyValueIfNeeded(set, "defaultValue", QVariant(false), changeDataTypeCommand, false /*!forceAddCommand*/, false /*!rememberOldValue*/); } if (set["primaryKey"].value().toBool() == true) { //primary keys require big int, so if selected type is not integer- remove PK if (fieldTypeGroup != KDbField::IntegerGroup) { /*not needed, line below will do the work d->view->data()->updateRecordEditBuffer(record, COLUMN_ID_ICON, QVariant()); d->view->data()->saveRecordChanges(record); */ //set["primaryKey"] = QVariant(false); d->setPropertyValueIfNeeded(set, "primaryKey", QVariant(false), changeDataTypeCommand); //! @todo should we display (passive?) dialog informing about cleared pkey? } } d->setPropertyValueIfNeeded(set, "subType", subTypeValue, changeDataTypeCommand, false, false /*!rememberOldValue*/, listData); if (d->updatePropertiesVisibility(fieldType, set, changeDataTypeCommand) || forcePropertySetReload) { //properties' visiblility changed: refresh prop. set propertySetReloaded(true); } addHistoryCommand(changeDataTypeCommand, false /* !execute */); } else if (colnum == COLUMN_ID_DESC) {//'description' KPropertySet *propertySetForRecord = d->sets->findPropertySetForItem(*data); if (!propertySetForRecord) return; //update field desc. QVariant oldValue((*propertySetForRecord)["description"].value()); qDebug() << oldValue; propertySetForRecord->changeProperty("description", *newValue); } } void KexiTableDesignerView::slotRecordUpdated(KDbRecordData *data) { const int record = d->view->data()->indexOf(data); if (record < 0) return; setDirty(); //-check if the record was empty before updating //if yes: we want to add a property set for this new record (field) QString fieldCaption(data->at(COLUMN_ID_CAPTION).toString()); const bool prop_set_allowed = !data->at(COLUMN_ID_TYPE).isNull(); if (!prop_set_allowed && d->sets->at(record)/*propertySet()*/) { //there is a property set, but it's not allowed - remove it: d->sets->eraseAt(record); //clear 'type' column: d->view->data()->clearRecordEditBuffer(); d->view->data()->updateRecordEditBuffer(data, COLUMN_ID_TYPE, QVariant()); d->view->data()->saveRecordChanges(data); } else if (prop_set_allowed && !d->sets->at(record)/*propertySet()*/) { //-- create a new field: KDbField::TypeGroup fieldTypeGroup = static_cast( data->at(COLUMN_ID_TYPE).toInt() + 1/*counting from 1*/); int intFieldType = KDb::defaultFieldTypeForGroup(fieldTypeGroup); if (intFieldType == 0) return; QString description(data->at(COLUMN_ID_DESC).toString()); //! @todo check uniqueness: QString fieldName(KDb::stringToIdentifier(fieldCaption)); KDbField::Type fieldType = KDb::intToFieldType(intFieldType); int maxLength = 0; if (fieldType == KDbField::Text) { maxLength = KDbField::defaultMaxLength(); } KDbField field( //tmp fieldName, fieldType, KDbField::NoConstraints, KDbField::NoOptions, maxLength, /*precision*/0, /*defaultValue*/QVariant(), fieldCaption, description); // reasonable case for boolean type: set notNull flag and "false" as default value switch (fieldType) { case KDbField::Boolean: field.setNotNull(true); field.setDefaultValue(QVariant(false)); break; case KDbField::Text: field.setMaxLengthStrategy(KDbField::DefaultMaxLength); break; default:; } qDebug() << field; //create a new property set: KPropertySet *newSet = createPropertySet(record, field, true); //refresh property editor: propertySetSwitched(); if (d->addHistoryCommand_in_slotRecordUpdated_enabled) { addHistoryCommand(new InsertFieldCommand(0, this, record, *newSet /*propertySet()*/), //, field /*will be copied*/ false /* !execute */); } } } void KexiTableDesignerView::updateActions() { updateActions(false); } void KexiTableDesignerView::slotPropertyChanged(KPropertySet& set, KProperty& property) { const QByteArray pname(property.name()); qDebug() << pname << " = " << property.value() << " (oldvalue = " << property.oldValue() << ")"; // true if PK should be altered bool changePrimaryKey = false; // true if PK should be set to true, otherwise unset bool setPrimaryKey = false; if (pname == "primaryKey" && d->slotPropertyChanged_primaryKey_enabled) { changePrimaryKey = true; setPrimaryKey = property.value().toBool(); } // update "lookup column" icon if (pname == "rowSource" || pname == "rowSourceType") { //! @todo indicate invalid definitions of lookup columns as well using a special icon //! (e.g. due to missing data source) const int record = d->sets->findRecordForPropertyValue("uid", set["uid"].value().toInt()); KDbRecordData *data = d->view->recordAt(record); if (data) d->updateIconForRecord(data, &set); } //setting autonumber requires setting PK as well Command *setAutonumberCommand = 0; Command *toplevelCommand = 0; if (pname == "autoIncrement" && property.value().toBool() == true) { if (set["primaryKey"].value().toBool() == false) {//we need PKEY here! QString msg = xi18n("Setting autonumber requires primary key to be set for current field."); if (d->primaryKeyExists) msg += xi18n("Previous primary key will be deleted."); msg += xi18n("Do you want to create primary key for current field? " "Click Cancel to cancel setting autonumber."); if (KMessageBox::Yes == KMessageBox::questionYesNo(this, msg, xi18n("Setting Autonumber Field"), KGuiItem(xi18nc("@action:button", "Create &Primary Key"), KexiIconName("database-key")), KStandardGuiItem::cancel())) { changePrimaryKey = true; setPrimaryKey = true; //switchPrimaryKey(set, true); // this will be toplevel command setAutonumberCommand = new Command( kundo2_i18n("Set autonumber for field %1", set["name"].value().toString()), 0, this); toplevelCommand = setAutonumberCommand; d->setPropertyValueIfNeeded(set, "autoIncrement", QVariant(true), setAutonumberCommand); } else { setAutonumberCommand = new Command( kundo2_i18n("Delete autonumber from field %1", set["name"].value().toString()), 0, this); d->setPropertyValueIfNeeded(set, "autoIncrement", QVariant(false), setAutonumberCommand, true /*forceAddCommand*/, false/*rememberOldValue*/); addHistoryCommand(setAutonumberCommand, false /* !execute */); return; } } } //clear PK when these properties were set to false: if ((pname == "indexed" || pname == "unique" || pname == "notNull") && set["primaryKey"].value().toBool() && property.value().toBool() == false) { //! @todo perhaps show a hint in help panel telling what happens? changePrimaryKey = true; setPrimaryKey = false; // this will be toplevel command Command *unsetIndexedOrUniquOrNotNullCommand = new Command( kundo2_i18n("Set %1 property for field %2", property.caption(), set["name"].value().toString()), 0, this); toplevelCommand = unsetIndexedOrUniquOrNotNullCommand; d->setPropertyValueIfNeeded(set, pname, QVariant(false), unsetIndexedOrUniquOrNotNullCommand); if (pname == "notNull") { d->setPropertyValueIfNeeded(set, "unique", QVariant(false), unsetIndexedOrUniquOrNotNullCommand); } } if (pname == "defaultValue") { KDbField::Type type = KDb::intToFieldType(set["type"].value().toInt()); set["defaultValue"].setType(static_cast(KDbField::variantType(type))); } if (pname == "subType" && d->slotPropertyChanged_subType_enabled) { d->slotPropertyChanged_subType_enabled = false; if (set["primaryKey"].value().toBool() == true && property.value().toString() != KDbField::typeString(KDbField::BigInteger)) { qDebug() << "INVALID " << property.value().toString(); // if (KMessageBox::Yes == KMessageBox::questionYesNo(this, msg, // xi18n("This field has primary key assigned. Setting autonumber field"), // KGuiItem(xi18nc("@action:button", "Create &Primary Key"), KexiIconName("database-key")), KStandardGuiItem::cancel() )) } KDbField::Type type = KDb::intToFieldType(set["type"].value().toInt()); QString typeName; /* disabled - "mime" is moved from subType to "objectType" custom property if (type==KDbField::BLOB) { //special case //find i18n'd text QStringList stringsList, namesList; getSubTypeListData(KDbField::BLOBGroup, stringsList, namesList); const int stringIndex = stringsList.findIndex( property.value().toString() ); if (-1 == stringIndex || stringIndex>=(int)namesList.count()) typeName = property.value().toString(); //for sanity else typeName = namesList[stringIndex]; } else {*/ typeName = KDbField::typeName(KDbField::typeForString(property.value().toString())); Command* changeFieldTypeCommand = new Command( kundo2_i18n( "Change type for field %1 to %2", set["name"].value().toString(), typeName), 0, this); d->setPropertyValueIfNeeded(set, "subType", property.value(), property.oldValue(), changeFieldTypeCommand); qDebug() << set["type"].value(); const KDbField::Type newType = KDbField::typeForString(property.value().toString()); set["type"].setValue(newType); // cast "defaultValue" property value to a new type QVariant oldDefVal(set["defaultValue"].value()); QVariant newDefVal(tryCastQVariant(oldDefVal, KDbField::variantType(type))); if (oldDefVal.type() != newDefVal.type()) set["defaultValue"].setType(newDefVal.type()); d->setPropertyValueIfNeeded(set, "defaultValue", newDefVal, newDefVal, changeFieldTypeCommand); d->updatePropertiesVisibility(newType, set); //properties' visiblility changed: refresh prop. set propertySetReloaded(true); d->slotPropertyChanged_subType_enabled = true; addHistoryCommand(changeFieldTypeCommand, false /* !execute */); return; } //! @todo add command text if ( d->addHistoryCommand_in_slotPropertyChanged_enabled && !changePrimaryKey/*we'll add multiple commands for PK*/) { addHistoryCommand(new ChangeFieldPropertyCommand(0, this, set, property.name(), property.oldValue() /* ??? */, property.value()), false /* !execute */); } if (changePrimaryKey) { d->slotPropertyChanged_primaryKey_enabled = false; if (setPrimaryKey) { //primary key implies some rules //this action contains subactions Command * setPrimaryKeyCommand = new Command( kundo2_i18n("Set primary key for field %1", set["name"].value().toString()), toplevelCommand, this); if (!toplevelCommand) { toplevelCommand = setPrimaryKeyCommand; } d->setPropertyValueIfNeeded(set, "primaryKey", QVariant(true), setPrimaryKeyCommand, true /*forceAddCommand*/); d->setPropertyValueIfNeeded(set, "unique", QVariant(true), setPrimaryKeyCommand); d->setPropertyValueIfNeeded(set, "notNull", QVariant(true), setPrimaryKeyCommand); d->setPropertyValueIfNeeded(set, "allowEmpty", QVariant(false), setPrimaryKeyCommand); d->setPropertyValueIfNeeded(set, "indexed", QVariant(true), setPrimaryKeyCommand); //! \todo: add setting for this: "Integer PKeys have autonumber set by default" d->setPropertyValueIfNeeded(set, "autoIncrement", QVariant(true), setPrimaryKeyCommand); /* set["unique"] = QVariant(true); set["notNull"] = QVariant(true); set["allowEmpty"] = QVariant(false); set["indexed"] = QVariant(true); set["autoIncrement"] = QVariant(true);*/ } else { // set PK to false //remember this action containing 2 subactions Command *setPrimaryKeyCommand = new Command( kundo2_i18n("Unset primary key for field %1", set["name"].value().toString()), toplevelCommand, this); if (!toplevelCommand) { toplevelCommand = setPrimaryKeyCommand; } d->setPropertyValueIfNeeded(set, "primaryKey", QVariant(false), setPrimaryKeyCommand, true /*forceAddCommand*/); d->setPropertyValueIfNeeded(set, "autoIncrement", QVariant(false), setPrimaryKeyCommand); } switchPrimaryKey(set, setPrimaryKey, true/*wasPKey*/, toplevelCommand); d->updatePropertiesVisibility( KDbField::typeForString(set["subType"].value().toString()), set, toplevelCommand); addHistoryCommand(toplevelCommand, false /* !execute */); //properties' visiblility changed: refresh prop. set propertySetReloaded(true/*preservePrevSelection*/); d->slotPropertyChanged_primaryKey_enabled = true; } } void KexiTableDesignerView::slotRecordInserted() { updateActions(); if (d->addHistoryCommand_in_slotRecordInserted_enabled) { const int record = d->view->currentRecord(); if (record >= 0) { addHistoryCommand(new InsertEmptyRecordCommand(0, this, record), false /* !execute */); } } //! @todo } void KexiTableDesignerView::slotAboutToDeleteRecord( KDbRecordData* data, KDbResultInfo* result, bool repaint) { Q_UNUSED(result) Q_UNUSED(repaint) if ((*data)[COLUMN_ID_ICON].toString() == KexiIconName("database-key")) d->primaryKeyExists = false; if (d->addHistoryCommand_in_slotAboutToDeleteRecord_enabled) { const int record = d->view->data()->indexOf(data); KPropertySet *set = record >= 0 ? d->sets->at(record) : 0; //set can be 0 here, what means "removing empty record" addHistoryCommand( new RemoveFieldCommand(0, this, record, set), false /* !execute */ ); } } KDbField * KexiTableDesignerView::buildField(const KPropertySet &set) const { //create a map of property values const KDbField::Type type = KDb::intToFieldType(set["type"].value().toInt()); QMap values(set.propertyValues()); //qDebug() << values; //remove internal values, to avoid creating custom field's properties KDbField *field = new KDbField(); for (QMutableMapIterator it(values); it.hasNext();) { it.next(); const QByteArray propName(it.key()); if (d->internalPropertyNames.contains(propName) || propName.startsWith("this:") || (/*sanity*/propName == "objectType" && type != KDbField::BLOB) || (propName == "unsigned" && !KDbField::isIntegerType(type)) || (propName == "maxLength" && type != KDbField::Text) || (propName == "precision" && !KDbField::isFPNumericType(type)) || (propName == "scale" && !KDbField::isFPNumericType(type)) ) { it.remove(); } } //assign properties to the field // (note that "objectType" property will be saved as custom property) if (!KDb::setFieldProperties(field, values)) { delete field; return 0; } return field; } tristate KexiTableDesignerView::buildSchema(KDbTableSchema &schema, bool beSilent) { if (!d->view->acceptRecordEditing()) return cancelled; //check for missing captions KPropertySet *b = 0; bool no_fields = true; int i; QSet names; for (i = 0; i < (int)d->sets->size(); i++) { b = d->sets->at(i); if (b) { no_fields = false; const QString name((*b)["name"].value().toString().toLower()); if (name.isEmpty()) { if (beSilent) { qWarning() << QString("no field caption entered at record %1...").arg(i + 1); } else { d->view->setCursorPosition(i, COLUMN_ID_CAPTION); d->view->startEditCurrentCell(); KMessageBox::information(this, xi18n("You should enter field caption.")); } return cancelled; } if (names.contains(name)) { break; } names.insert(name); //remember } } //check for empty design if (no_fields) { if (beSilent) { qWarning() << "no field defined..."; } else { KMessageBox::sorry(this, xi18n("You have added no fields.\nEvery table should have at least one field.")); } return cancelled; } //check for duplicates if (b && i < (int)d->sets->size()) { if (beSilent) { qWarning() << QString("duplicated field name '%1'") .arg((*b)["name"].value().toString()); } else { d->view->setCursorPosition(i, COLUMN_ID_CAPTION); d->view->startEditCurrentCell(); //! @todo for "names hidden" mode we won't get this error because user is unable to change names KMessageBox::sorry(this, xi18nc("@info", "You have added %1 field name twice." "Field names cannot be repeated. Correct name of the field.", (*b)["name"].value().toString())); } return cancelled; } //check for pkey; automatically add a pkey if user wanted if (!d->primaryKeyExists) { if (beSilent) { qDebug() << "no primay key defined..."; } else { const int questionRes = KMessageBox::questionYesNoCancel(this, xi18nc("@info", "Table %1 has no primary key defined." "Although a primary key is not required, it is needed " "for creating relations between database tables. " "Do you want a primary key to be automatically added now?" "If you want to add a primary key by hand, press Cancel " "to cancel saving table design.", schema.name()), QString(), KGuiItem(xi18nc("@action:button Add Database Primary Key to a Table", "&Add Primary Key"), KexiIconName("database-key")), KGuiItem(xi18nc("@action:button Do Not Add Database Primary Key to a Table", "Do &Not Add"), KStandardGuiItem::no().icon()), KStandardGuiItem::cancel(), "autogeneratePrimaryKeysOnTableDesignSaving"); if (questionRes == KMessageBox::Cancel) { return cancelled; } else if (questionRes == KMessageBox::Yes) { //-find unique name, starting with, "id", "id2", .... int i = 0; int idIndex = 1; //means "id" QString pkFieldName("id%1"); KLocalizedString pkFieldCaption(kxi18nc("Identifier%1", "Id%1")); while (i < (int)d->sets->size()) { KPropertySet *set = d->sets->at(i); if (set) { if ( (*set)["name"].value().toString() == pkFieldName.arg(idIndex == 1 ? QString() : QString::number(idIndex)) || (*set)["caption"].value().toString() == pkFieldCaption.subs(idIndex == 1 ? QString() : QString::number(idIndex)).toString() ) { //try next id index i = 0; idIndex++; continue; } } i++; } pkFieldName = pkFieldName.arg(idIndex == 1 ? QString() : QString::number(idIndex)); //ok, add PK with such unique name d->view->insertEmptyRecord(0); d->view->setCursorPosition(0, COLUMN_ID_CAPTION); d->view->data()->updateRecordEditBuffer(d->view->selectedRecord(), COLUMN_ID_CAPTION, pkFieldCaption.subs(idIndex == 1 ? QString() : QString::number(idIndex)).toString() ); d->view->data()->updateRecordEditBuffer(d->view->selectedRecord(), COLUMN_ID_TYPE, QVariant(KDbField::IntegerGroup - 1/*counting from 0*/)); if (!d->view->data()->saveRecordChanges(d->view->selectedRecord(), true)) { return cancelled; } slotTogglePrimaryKey(); } } } //we're ready... for (i = 0;i < (int)d->sets->size();++i) {//for every field create Field definition KPropertySet *s = d->sets->at(i); if (!s) continue; KDbField * f = buildField(*s); if (!f) continue; //hmm? if (!schema.addField(f)) { qWarning() << "!schema.addField(f)"; return false; } if ( !(*s)["rowSource"].value().toString().isEmpty() && !(*s)["rowSourceType"].value().toString().isEmpty()) { //add lookup column KDbLookupFieldSchema *lookupFieldSchema = new KDbLookupFieldSchema(); KDbLookupFieldSchemaRecordSource recordSource; recordSource.setTypeByName((*s)["rowSourceType"].value().toString()); recordSource.setName((*s)["rowSource"].value().toString()); lookupFieldSchema->setRecordSource(recordSource); lookupFieldSchema->setBoundColumn((*s)["boundColumn"].value().toInt()); //! @todo this is backward-compatible code for "single visible column" implementation //! for multiple columns, only the first is displayed, so there is a data loss is GUI is used //! -- special kproperty editor needed QList visibleColumns; const int visibleColumn = (*s)["visibleColumn"].value().toInt(); if (visibleColumn >= 0) visibleColumns.append(visibleColumn); lookupFieldSchema->setVisibleColumns(visibleColumns); //! @todo support columnWidths(), columnHeadersVisible(), maxVisibleRecords(), limitToList(), displayWidget() if (!schema.setLookupFieldSchema(f->name(), lookupFieldSchema)) { qWarning() << "!schema.setLookupFieldSchema()"; delete lookupFieldSchema; return false; } } } return true; } //! @internal //! A recursive function for copying alter table actions from undo/redo commands. static void copyAlterTableActions(const KUndo2Command* command, KDbAlterTableHandler::ActionList &actions) { for (int i = 0; i < command->childCount(); ++i) { copyAlterTableActions(command->child(i), actions); } const Command* cmd = dynamic_cast(command); if (!cmd) { qWarning() << "cmd is not of type 'Command'!"; return; } KDbAlterTableHandler::ActionBase* action = cmd->createAction(); //some commands can contain null actions, e.g. "set visibility" command if (action) actions.append(action); } tristate KexiTableDesignerView::buildAlterTableActions( KDbAlterTableHandler::ActionList &actions) { actions.clear(); qDebug() << d->history->count() << " top-level command(s) to process..."; for (int i = 0; i < d->history->count(); ++i) { copyAlterTableActions(d->history->command(i), actions); } return true; } KDbObject* KexiTableDesignerView::storeNewData(const KDbObject& object, KexiView::StoreNewDataOptions options, bool *cancel) { Q_ASSERT(cancel); if (tempData()->table() || window()->schemaObject()) //must not be return 0; //create table schema definition tempData()->setTable(new KDbTableSchema(object.name())); tempData()->table()->setName(object.name()); tempData()->table()->setCaption(object.caption()); tempData()->table()->setDescription(object.description()); tristate res = buildSchema(*tempData()->table()); *cancel = ~res; //FINALLY: create table: if (res == true) { //! @todo KDbConnection *conn = KexiMainWindowIface::global()->project()->dbConnection(); KDbConnection::CreateTableOptions createOptions(KDbConnection::CreateTableOption::Default); if (options & KexiView::OverwriteExistingData) { createOptions |= KDbConnection::CreateTableOption::DropDestination; } res = conn->createTable(tempData()->table(), createOptions); if (res == true) { res = KexiMainWindowIface::global()->project()->removeUserDataBlock(tempData()->table()->id()); } else { window()->setStatus(conn, ""); } } if (res == true) { //we've current schema tempData()->tableSchemaChangedInPreviousView = true; d->history->clear(); } else { KDbTableSchema *tableToDelete = tempData()->table(); tempData()->setTable(nullptr); delete tableToDelete; } return tempData()->table(); } KDbObject* KexiTableDesignerView::copyData(const KDbObject& object, KexiView::StoreNewDataOptions options, bool *cancel) { Q_ASSERT(cancel); Q_UNUSED(options); Q_UNUSED(cancel); if (!tempData()->table()) { qWarning() << "Cannot copy data without source table (tempData()->table)"; return 0; } KDbConnection *conn = KexiMainWindowIface::global()->project()->dbConnection(); KDbTableSchema *copiedTable = conn->copyTable(*tempData()->table(), object); if (!copiedTable) { return 0; } if (!KexiMainWindowIface::global()->project()->copyUserDataBlock(tempData()->table()->id(), copiedTable->id())) { conn->dropTable(copiedTable); delete copiedTable; return 0; } return copiedTable; } tristate KexiTableDesignerView::storeData(bool dontAsk) { if (!tempData()->table() || !window()->schemaObject()) { d->recentResultOfStoreData = false; return false; } KDbConnection *conn = KexiMainWindowIface::global()->project()->dbConnection(); KDbAlterTableHandler *alterTableHandler = 0; KDbTableSchema *newTable = 0; //- create action list for the alter table handler KDbAlterTableHandler::ActionList actions; tristate res = buildAlterTableActions(actions); //!< @todo this is temporary flag before we switch entirely to real alter table bool realAlterTableCanBeUsed = false; if (res == true) { alterTableHandler = new KDbAlterTableHandler(conn); alterTableHandler->setActions(actions); if (!d->tempStoreDataUsingRealAlterTable) { //only compute requirements KDbAlterTableHandler::ExecutionArguments args; args.onlyComputeRequirements = true; (void)alterTableHandler->execute(tempData()->table()->name(), &args); res = args.result; if ( res == true && 0 == (args.requirements & (0xffff ^ KDbAlterTableHandler::SchemaAlteringRequired))) { realAlterTableCanBeUsed = true; } } } if (res == true) { res = KexiTablePart::askForClosingObjectsUsingTableSchema( - window(), conn, tempData()->table(), - xi18nc("@info", - "You are about to change the design of table %1 " - "but following objects using this table are opened:", - tempData()->table()->name())); + window(), conn, tempData()->table(), + kxi18nc("@info", + "You are about to change the design of table %1 " + "but following objects using this table are open:") + .subs(tempData()->table()->name())); } if (res == true) { if (!d->tempStoreDataUsingRealAlterTable && !realAlterTableCanBeUsed) { //! @todo temp; remove this case: delete alterTableHandler; alterTableHandler = 0; // - inform about removing the current table and ask for confirmation if (!d->dontAskOnStoreData && !dontAsk) { bool emptyTable; const QString msg = d->messageForSavingChanges(&emptyTable).toString(); if (!emptyTable) { if (KMessageBox::No == KMessageBox::questionYesNo( this, msg, QString(), KStandardGuiItem::save(), KStandardGuiItem::dontSave(), QString(), KMessageBox::Notify | KMessageBox::Dangerous)) { res = cancelled; } } } d->dontAskOnStoreData = false; //one-time use if (~res) { d->recentResultOfStoreData = res; return res; } // keep old behaviour: newTable = new KDbTableSchema(); // copy the object data static_cast(*newTable) = static_cast(*tempData()->table()); res = buildSchema(*newTable); qDebug() << "BUILD SCHEMA:" << *newTable; - - res = conn->alterTable(tempData()->table(), newTable); + { + KDbTableSchema *oldTable = tempData()->table(); + tempData()->setTable(nullptr); // needed, otherwise setTable() will access dangling + // pointer after conn->alterTable() + KexiUtils::BoolBlocker guard(&tempData()->closeWindowOnCloseListener, false); + res = conn->alterTable(oldTable, newTable); + } if (res != true) window()->setStatus(conn, ""); } else { KDbAlterTableHandler::ExecutionArguments args; newTable = alterTableHandler->execute(tempData()->table()->name(), &args); res = args.result; qDebug() << "ALTER TABLE EXECUTE: " << res.toString(); if (true != res) { qDebug() << alterTableHandler->result(); window()->setStatus(alterTableHandler, ""); } } } if (res == true) { //change current schema tempData()->setTable(newTable); tempData()->tableSchemaChangedInPreviousView = true; d->history->clear(); } else { delete newTable; } delete alterTableHandler; d->recentResultOfStoreData = res; return res; } tristate KexiTableDesignerView::simulateAlterTableExecution(QString *debugTarget) { #ifndef KEXI_NO_UNDOREDO_ALTERTABLE # ifdef KEXI_DEBUG_GUI if (KexiMainWindowIface::global()->currentWindow() != window()) { //to avoid executing for multiple alter table views return false; } if (!tempData()->table() || !window()->schemaObject()) return false; KDbConnection *conn = KexiMainWindowIface::global()->project()->dbConnection(); KDbAlterTableHandler::ActionList actions; /*tristate res =*/ buildAlterTableActions(actions); //! @todo result? KDbAlterTableHandler alterTableHandler(conn); alterTableHandler.setActions(actions); KDbAlterTableHandler::ExecutionArguments args; if (debugTarget) { args.debugString = debugTarget; } else { args.simulate = true; } (void)alterTableHandler.execute(tempData()->table()->name(), &args); return args.result; # else Q_UNUSED(debugTarget); return false; # endif #else Q_UNUSED(debugTarget); return false; #endif } void KexiTableDesignerView::slotSimulateAlterTableExecution() { (void)simulateAlterTableExecution(0); } tristate KexiTableDesignerView::executeRealAlterTable() { d->tempStoreDataUsingRealAlterTable = true; d->recentResultOfStoreData = false; // will call KexiMainWindow::slotProjectSaveAs() and thus storeData(): QMetaObject::invokeMethod( KexiMainWindowIface::global()->thisWidget(), "slotProjectSave"); d->tempStoreDataUsingRealAlterTable = false; return d->recentResultOfStoreData; } KexiTablePartTempData* KexiTableDesignerView::tempData() const { return static_cast(window()->data()); } #ifdef KEXI_DEBUG_GUI void KexiTableDesignerView::debugCommand(const KUndo2Command* command, int nestingLevel) { const Command* kexiCommand = dynamic_cast(command); if (kexiCommand) { KDb::alterTableActionDebugGUI(kexiCommand->debugString(), nestingLevel); } else { KDb::alterTableActionDebugGUI(command->text().toString(), nestingLevel); } //show subcommands for (int i = 0; i < command->childCount(); ++i) { debugCommand(command->child(i), nestingLevel + 1); } } #endif void KexiTableDesignerView::addHistoryCommand(KexiTableDesignerCommands::Command* command, bool execute) { #ifdef KEXI_NO_UNDOREDO_ALTERTABLE Q_UNUSED(command); Q_UNUSED(execute); #else # ifdef KEXI_DEBUG_GUI debugCommand(command, 0); # endif if (!execute) { command->blockRedoOnce(); } d->history->push(command); updateUndoRedoActions(); #endif } void KexiTableDesignerView::updateUndoRedoActions() { #ifndef KEXI_NO_UNDOREDO_ALTERTABLE setAvailable("edit_undo", d->historyActionCollection->action("edit_undo")->isEnabled()); setAvailable("edit_redo", d->historyActionCollection->action("edit_redo")->isEnabled()); #endif } void KexiTableDesignerView::slotUndo() { #ifndef KEXI_NO_UNDOREDO_ALTERTABLE # ifdef KEXI_DEBUG_GUI KDb::alterTableActionDebugGUI(QString("UNDO:")); # endif d->history->undo(); updateUndoRedoActions(); #endif } void KexiTableDesignerView::slotRedo() { #ifndef KEXI_NO_UNDOREDO_ALTERTABLE # ifdef KEXI_DEBUG_GUI KDb::alterTableActionDebugGUI(QString("REDO:")); # endif d->history->redo(); updateUndoRedoActions(); #endif } void KexiTableDesignerView::slotAboutToShowContextMenu() { //update title QString title; if (propertySet()) { const KPropertySet &set = *propertySet(); QString captionOrName(set["caption"].value().toString()); if (captionOrName.isEmpty()) captionOrName = set["name"].value().toString(); title = xi18nc("@info", "Table field %1", captionOrName); } else { title = xi18nc("Empty table row", "Empty Row"); } //! \todo replace lineedit with table_field icon d->view->setContextMenuTitle(KexiIcon("lineedit"), title); } QString KexiTableDesignerView::debugStringForCurrentTableSchema(tristate& result) { KDbTableSchema tempTable; //copy object data static_cast(tempTable) = static_cast(*tempData()->table()); result = buildSchema(tempTable, true /*beSilent*/); if (true != result) { return QString(); } return KDbUtils::debugString(tempTable); } // -- low-level actions used by undo/redo framework void KexiTableDesignerView::clearRecord(int record, bool addCommand) { if (!d->view->acceptRecordEditing()) return; KDbRecordData *data = d->view->recordAt(record); if (!data) return; //clear from prop. set d->sets->eraseAt(record); //clear record in table view (just clear value in COLUMN_ID_TYPE column) if (!addCommand) { d->addHistoryCommand_in_slotRecordUpdated_enabled = false; d->addHistoryCommand_in_slotPropertyChanged_enabled = false; d->slotBeforeCellChanged_enabled = false; } d->view->data()->updateRecordEditBuffer(data, COLUMN_ID_TYPE, QVariant()); if (!addCommand) { d->addHistoryCommand_in_slotRecordUpdated_enabled = true; d->addHistoryCommand_in_slotPropertyChanged_enabled = true; d->slotBeforeCellChanged_enabled = true; } d->view->data()->saveRecordChanges(data, true); } void KexiTableDesignerView::insertField(int record, const QString& caption, bool addCommand) { insertFieldInternal(record, 0, caption, addCommand); } void KexiTableDesignerView::insertField(int record, KPropertySet& set, bool addCommand) { insertFieldInternal(record, &set, QString(), addCommand); } void KexiTableDesignerView::insertFieldInternal(int record, KPropertySet* set, //const KDbField& field, const QString& caption, bool addCommand) { if (set && (!set->contains("type") || !set->contains("caption"))) { qWarning() << "no 'type' or 'caption' property in set!"; return; } if (!d->view->acceptRecordEditing()) return; KDbRecordData *data = d->view->recordAt(record); if (!data) return; if (!addCommand) { d->addHistoryCommand_in_slotRecordUpdated_enabled = false; d->addHistoryCommand_in_slotPropertyChanged_enabled = false; d->slotBeforeCellChanged_enabled = false; } d->view->data()->updateRecordEditBuffer(data, COLUMN_ID_CAPTION, set ? (*set)["caption"].value() : QVariant(caption)); KDbField::TypeGroup tg; if (set) { tg = KDbField::typeGroup(KDb::intToFieldType((*set)["type"].value().toInt())); } else { tg = KDbField::TextGroup; // default type } d->view->data()->updateRecordEditBuffer(data, COLUMN_ID_TYPE, int(tg) - 1); d->view->data()->updateRecordEditBuffer(data, COLUMN_ID_DESC, set ? (*set)["description"].value() : QVariant()); if (!addCommand) { d->slotBeforeCellChanged_enabled = true; } //this will create a new property set: d->view->data()->saveRecordChanges(data); if (set) { KPropertySet *newSet = d->sets->at(record); if (newSet) { *newSet = *set; //deep copy } else { qWarning() << "!newSet, record==" << record; } } if (!addCommand) { d->addHistoryCommand_in_slotPropertyChanged_enabled = true; d->addHistoryCommand_in_slotRecordUpdated_enabled = true; } d->view->updateRecord(record); propertySetReloaded(true); } void KexiTableDesignerView::insertEmptyRecord(int record, bool addCommand) { if (!addCommand) { d->addHistoryCommand_in_slotRecordInserted_enabled = false; } d->view->insertEmptyRecord(record); if (!addCommand) { d->addHistoryCommand_in_slotRecordInserted_enabled = true; } } void KexiTableDesignerView::deleteRecord(int record, bool addCommand) { KDbRecordData *data = d->view->recordAt(record); if (!data) return; if (!addCommand) { d->addHistoryCommand_in_slotAboutToDeleteRecord_enabled = false; } const bool res = d->view->deleteItem(data); if (!addCommand) { d->addHistoryCommand_in_slotAboutToDeleteRecord_enabled = true; } if (!res) return; } void KexiTableDesignerView::changeFieldPropertyForRecord(int record, const QByteArray& propertyName, const QVariant& newValue, const KPropertyListData* listData, bool addCommand) { #ifdef KEXI_DEBUG_GUI KDb::alterTableActionDebugGUI(QString("** changeFieldProperty: \"") + QString(propertyName) + "\" to \"" + newValue.toString() + "\"", 2/*nestingLevel*/); #endif if (!d->view->acceptRecordEditing()) return; KPropertySet* set = d->sets->at(record); if (!set || !set->contains(propertyName)) return; KProperty &property = set->property(propertyName); if (listData) { if (listData->keys().isEmpty()) { property.setListData(nullptr); } else { property.setListData(new KPropertyListData(*listData)); } } if (propertyName != "type") //delayed type update (we need to have subtype set properly) property.setValue(newValue); KDbRecordData *data = d->view->recordAt(record); Q_ASSERT(data); if (propertyName == "type") { d->slotPropertyChanged_subType_enabled = false; d->view->data()->updateRecordEditBuffer(data, COLUMN_ID_TYPE, int(KDbField::typeGroup(KDb::intToFieldType(newValue.toInt()))) - 1); d->view->data()->saveRecordChanges(data); d->addHistoryCommand_in_slotRecordUpdated_enabled = true; property.setValue(newValue); //delayed type update (we needed to have subtype set properly) } if (!addCommand) { d->addHistoryCommand_in_slotRecordUpdated_enabled = false; d->addHistoryCommand_in_slotPropertyChanged_enabled = false; d->slotPropertyChanged_subType_enabled = false; } //special cases: properties displayed within the data grid: if (propertyName == "caption") { if (!addCommand) { d->slotBeforeCellChanged_enabled = false; } d->view->data()->updateRecordEditBuffer(data, COLUMN_ID_CAPTION, newValue); d->view->data()->saveRecordChanges(data); if (!addCommand) { d->slotBeforeCellChanged_enabled = true; } } else if (propertyName == "description") { if (!addCommand) { d->slotBeforeCellChanged_enabled = false; } d->view->data()->updateRecordEditBuffer(data, COLUMN_ID_DESC, newValue); if (!addCommand) { d->slotBeforeCellChanged_enabled = true; } d->view->data()->saveRecordChanges(data); } if (!addCommand) { d->addHistoryCommand_in_slotPropertyChanged_enabled = true; d->addHistoryCommand_in_slotRecordUpdated_enabled = true; d->slotPropertyChanged_subType_enabled = true; } d->view->updateRecord(record); } void KexiTableDesignerView::changeFieldProperty(int fieldUID, const QByteArray& propertyName, const QVariant& newValue, const KPropertyListData* listData, bool addCommand) { //find a property by UID const int record = d->sets->findRecordForPropertyValue("uid", fieldUID); if (record < 0) { qWarning() << "field with uid=" << fieldUID << " not found!"; return; } changeFieldPropertyForRecord(record, propertyName, newValue, listData, addCommand); } void KexiTableDesignerView::changePropertyVisibility( int fieldUID, const QByteArray& propertyName, bool visible) { #ifdef KEXI_DEBUG_GUI KDb::alterTableActionDebugGUI(QString("** changePropertyVisibility: \"") + QString(propertyName) + "\" to \"" + (visible ? "true" : "false") + "\"", 2/*nestingLevel*/); #endif if (!d->view->acceptRecordEditing()) return; //find a property by name const int record = d->sets->findRecordForPropertyValue("uid", fieldUID); if (record < 0) return; KPropertySet* set = d->sets->at(record); if (!set || !set->contains(propertyName)) return; KProperty &property = set->property(propertyName); if (property.isVisible() != visible) { property.setVisible(visible); propertySetReloaded(true); } } void KexiTableDesignerView::propertySetSwitched() { KexiDataTableView::propertySetSwitched(); KexiLookupColumnPage *page = qobject_cast(window()->part())->lookupColumnPage(); if (page) page->assignPropertySet(propertySet()); } bool KexiTableDesignerView::isPhysicalAlteringNeeded() { //- create action list for the alter table handler KDbAlterTableHandler::ActionList actions; tristate res = buildAlterTableActions(actions); if (res != true) return true; KDbConnection *conn = KexiMainWindowIface::global()->project()->dbConnection(); KDbAlterTableHandler *alterTableHandler = new KDbAlterTableHandler(conn); alterTableHandler->setActions(actions); //only compute requirements KDbAlterTableHandler::ExecutionArguments args; args.onlyComputeRequirements = true; (void)alterTableHandler->execute(tempData()->table()->name(), &args); res = args.result; delete alterTableHandler; if ( res == true && 0 == (args.requirements & (0xffff ^ KDbAlterTableHandler::SchemaAlteringRequired))) { return false; } return true; } diff --git a/src/plugins/tables/kexitabledesignerview.h b/src/plugins/tables/kexitabledesignerview.h index 9284484f7..68f522574 100644 --- a/src/plugins/tables/kexitabledesignerview.h +++ b/src/plugins/tables/kexitabledesignerview.h @@ -1,252 +1,252 @@ /* This file is part of the KDE project - Copyright (C) 2004-2012 Jarosław Staniek + Copyright (C) 2004-2017 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 KEXITABLEDESIGNERINTERVIEW_H #define KEXITABLEDESIGNERINTERVIEW_H #include #include #include #include #include "kexitablepart.h" class KDbRecordData; class KPropertySet; class KUndo2Command; class KexiTableDesignerViewPrivate; namespace KexiTableDesignerCommands { class Command; } //! Design view of the Table Designer /*! Contains a spreadsheet-like space for entering field definitions. Property editor is provided for altering field definitions. The view also supports Undo and Redo operations. These are connected to a factility creating a list of actions used by AlterTableHandler to perform required operation of altering the table. Altering itself is performed upon design saving (storeData()). Saving unstored designs just creates a new table. Saving changes made to empty (not filled with data) table is performed by physically deleting the previous table schema and recreating it TODO: this will be not quite when we have db relationships supported. Saving changes made to table containing data requires use of the AlterTableHandler functionality. */ class KexiTableDesignerView : public KexiDataTableView, public KexiTableDesignerInterface { Q_OBJECT public: /*! Creates a new alter table dialog. */ explicit KexiTableDesignerView(QWidget *parent); virtual ~KexiTableDesignerView(); KexiTablePartTempData* tempData() const; /*! Clears field information entered for record. This is performed by removing values from caption and data type columns. Used by InsertFieldCommand to undo inserting a new field. */ virtual void clearRecord(int record, bool addCommand = false); /*! Inserts a new field with \a caption for \a record. Property set is also created. */ virtual void insertField(int record, const QString& caption, bool addCommand = false); /*! Inserts a new \a field for \a record. Property set is also created. \a set will be deeply-copied into the new set. Used by InsertFieldCommand to insert a new field. */ virtual void insertField(int record, KPropertySet& set, bool addCommand = false); /*! Inserts a new empty record at position \a record. Used by RemoveFieldCommand as a part of undo inserting a new field; also used by InsertEmptyRecordCommand. */ virtual void insertEmptyRecord(int record, bool addCommand = false); /*! Deletes \a record from the table view. Property set is also deleted. All the subsequent fields are moved up. Used for undoing InsertEmptyRecordCommand and by RemoveFieldCommand to remove a field. */ virtual void deleteRecord(int record, bool addCommand = false); /*! Changes property \a propertyName to \a newValue for a field at record \a record. If \a listData is not NULL and not empty, a deep copy of it is passed to Property::setListData(). If \a listData \a nlist if not NULL but empty, Property::setListData(0) is called. */ virtual void changeFieldPropertyForRecord(int record, const QByteArray& propertyName, const QVariant& newValue, const KPropertyListData* listData, bool addCommand); /*! Changes property \a propertyName to \a newValue. Works exactly like changeFieldPropertyForRecord(); except the field is pointed by \a fieldUID. Used by ChangeFieldPropertyCommand to change field's property. */ void changeFieldProperty(int fieldUID, const QByteArray& propertyName, const QVariant& newValue, const KPropertyListData* listData = nullptr, bool addCommand = false); /*! Changes visibility of property \a propertyName to \a visible for a field pointed by \a fieldUID. Used by ChangePropertyVisibilityCommand. */ void changePropertyVisibility(int fieldUID, const QByteArray& propertyName, bool visible); /*! Builds table field's schema by looking at the \a set. */ KDbField * buildField(const KPropertySet &set) const; /*! Creates temporary table for the current design and returns debug string for it. */ virtual QString debugStringForCurrentTableSchema(tristate& result); /*! Simulates execution of alter table, and puts debug into \a debugTarget. A case when debugTarget is not 0 is true for the alter table test suite. */ virtual tristate simulateAlterTableExecution(QString *debugTarget); public Q_SLOTS: /*! Real execution of the Alter Table. For debugging of the real alter table. \return true on success, false on failure and cancelled if user has cancelled execution. */ virtual tristate executeRealAlterTable(); protected Q_SLOTS: /*! Equivalent to updateActions(false). Called on record insert/delete in a KexiDataAwarePropertySet. */ void updateActions(); virtual void slotUpdateRecordActions(int record); void slotAboutToShowContextMenu(); //! Called before cell change in tableview. void slotBeforeCellChanged(KDbRecordData *data, int colnum, QVariant* newValue, KDbResultInfo* result); //! Called on record change in a tableview. void slotRecordUpdated(KDbRecordData *data); //! Called before record inserting in tableview. void slotRecordInserted(); //! Called before record deleting in tableview. void slotAboutToDeleteRecord(KDbRecordData* data, KDbResultInfo* result, bool repaint); /*! Called after any property has been changed in the current property set, to perform some actions (like updating other dependent properties) */ void slotPropertyChanged(KPropertySet& set, KProperty& property); /*! Toggles primary key for currently selected field. Does nothing for empty record. */ void slotTogglePrimaryKey(); /*! Undoes the recently performed action. */ void slotUndo(); /*! Redoes the recently undoed action. */ void slotRedo(); /*! Simulates real execution of the Alter Table. For debugging. */ void slotSimulateAlterTableExecution(); protected: virtual void updateActions(bool activated); //! called whenever data should be reloaded (on switching to this view mode) void initData(); /*! Creates a new property set for \a field. The property set will be asigned to \a record, and owned by this dialog. If \a newOne is true, the property set will be marked as newly created. \return newly created property set. */ KPropertySet* createPropertySet(int record, const KDbField& field, bool newOne = false); virtual tristate beforeSwitchTo(Kexi::ViewMode mode, bool *dontStore); virtual tristate afterSwitchFrom(Kexi::ViewMode mode); /*! \return property set associated with currently selected record (i.e. field) or 0 if current record is empty. */ virtual KPropertySet *propertySet(); /*! Reimplemented from KexiView, because tables creation is more complex. No table schema altering is required, so just buildSchema() is used to create a new schema. */ virtual KDbObject* storeNewData(const KDbObject& object, KexiView::StoreNewDataOptions options, bool *cancel); /*! Reimplemented from KexiView, because cloning of table objects is more complex. */ virtual KDbObject* copyData(const KDbObject& object, KexiView::StoreNewDataOptions options, bool *cancel); /*! Reimplemented from KexiView, because table storage is more complex. Table schema altering may be required, so just buildSchema() is used to create a new schema. */ virtual tristate storeData(bool dontAsk = false); /*! Builds table schema by looking at the current design. Used in storeNewData() and storeData(). If \a beSilent is true, no message boxes are used to show questions or warnings. This is used in the altertable test suite (kexi/tests/altertable). \return true on successful schema creating, false on failure and cancelled when there was a problem with user's design (and user has been informed about it). */ tristate buildSchema(KDbTableSchema &schema, bool beSilent = false); /*! Builds action list usable for KDbAlterTableHandler by looking at undo buffer of commands' history. Used in storeData() */ tristate buildAlterTableActions(KDbAlterTableHandler::ActionList &actions); /*! Helper, used for slotTogglePrimaryKey() and slotPropertyChanged(). Assigns primary key icon and value for property set \a propertySet, and deselects it from previous pkey's record. \a aWasPKey is internal. If \a commandGroup is not 0, it is used as parent group for storing actions' history. */ void switchPrimaryKey(KPropertySet &propertySet, bool set, bool aWasPKey = false, KexiTableDesignerCommands::Command* commandGroup = 0); /*! Adds history command \a command to the undo/redo buffer. If \a execute is true, the command is executed afterwards. */ void addHistoryCommand(KexiTableDesignerCommands::Command* command, bool execute); //! Updates undo/redo shared actions availability by looking at command history's action void updateUndoRedoActions(); #ifdef KEXI_DEBUG_GUI void debugCommand(const KUndo2Command* command, int nestingLevel); #endif /*! Inserts a new \a field for \a record. Property set is also created. If \a set is not 0 (the default), it will be copied into the new set. Used by insertField(). */ void insertFieldInternal(int record, KPropertySet* set, const QString& caption, bool addCommand); //! Reimplemented to pass the information also to the "Lookup" tab virtual void propertySetSwitched(); /*! \return true if physical altering is needed for the current list of actions. Used in KexiTableDesignerView::beforeSwitchTo() to avoid warning about removinf table data if table recreating is not needed. True is also returned if there is any trouble with getting the answer. */ bool isPhysicalAlteringNeeded(); private: KexiTableDesignerViewPrivate * const d; }; #endif diff --git a/src/plugins/tables/kexitablepart.cpp b/src/plugins/tables/kexitablepart.cpp index fac9e63d7..110cd7940 100644 --- a/src/plugins/tables/kexitablepart.cpp +++ b/src/plugins/tables/kexitablepart.cpp @@ -1,336 +1,361 @@ /* This file is part of the KDE project Copyright (C) 2003 Lucijan Busch Copyright (C) 2002, 2003 Joseph Wenninger Copyright (C) 2004-2017 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 "kexitablepart.h" #include #include #include #include #include #include #include #include #include "kexitabledesignerview.h" #include "kexitabledesigner_dataview.h" #include "kexilookupcolumnpage.h" #include +#include #include #include #include #include KEXI_PLUGIN_FACTORY(KexiTablePart, "kexi_tableplugin.json") //! @internal class Q_DECL_HIDDEN KexiTablePart::Private { public: Private() { } ~Private() { delete static_cast(lookupColumnPage); } QPointer lookupColumnPage; }; KexiTablePart::KexiTablePart(QObject *parent, const QVariantList& l) : KexiPart::Part(parent, xi18nc("Translate this word using only lowercase alphanumeric characters (a..z, 0..9). " "Use '_' character instead of spaces. First character should be a..z character. " "If you cannot use latin characters in your language, use english word.", "table"), xi18nc("tooltip", "Create new table"), xi18nc("what's this", "Creates new table."), l) , d(new Private) { // needed for custom "identifier" property editor widget KexiCustomPropertyFactory::init(); //! @todo js: also add Kexi::TextViewMode when we'll have SQL ALTER TABLE EDITOR!!! } KexiTablePart::~KexiTablePart() { delete d; } void KexiTablePart::initPartActions() { } void KexiTablePart::initInstanceActions() { } KexiWindowData* KexiTablePart::createWindowData(KexiWindow* window) { KexiMainWindowIface *win = KexiMainWindowIface::global(); return new KexiTablePartTempData(window, win->project()->dbConnection()); } KexiView* KexiTablePart::createView(QWidget *parent, KexiWindow* window, KexiPart::Item *item, Kexi::ViewMode viewMode, QMap*) { Q_ASSERT(item); KexiMainWindowIface *win = KexiMainWindowIface::global(); if (!win || !win->project() || !win->project()->dbConnection()) return 0; KexiTablePartTempData *temp = static_cast(window->data()); if (!temp->table()) { temp->setTable(win->project()->dbConnection()->tableSchema(item->name())); qDebug() << "schema is " << temp->table(); } if (viewMode == Kexi::DesignViewMode) { KexiTableDesignerView *t = new KexiTableDesignerView(parent); return t; } else if (viewMode == Kexi::DataViewMode) { if (!temp->table()) { return 0; //!< @todo message } //we're not setting table schema here -it will be forced to set // in KexiTableDesigner_DataView::afterSwitchFrom() KexiTableDesigner_DataView *t = new KexiTableDesigner_DataView(parent); return t; } return 0; } tristate KexiTablePart::remove(KexiPart::Item *item) { KexiProject *project = KexiMainWindowIface::global()->project(); if (!project || !project->dbConnection()) return false; KDbConnection *conn = project->dbConnection(); KDbTableSchema *sch = conn->tableSchema(item->identifier()); if (sch) { const tristate res = KexiTablePart::askForClosingObjectsUsingTableSchema( KexiMainWindowIface::global()->openedWindowFor(item->identifier()), conn, sch, - xi18n("You are about to remove table %1 but following objects using this table are opened:", - sch->name())); + kxi18n("You are about to delete table %1 but it is used by " + "following opened windows:") + .subs(sch->name())); if (res != true) { return res; } return conn->dropTable(sch); } //last chance: just remove item return conn->removeObject(item->identifier()); } tristate KexiTablePart::rename(KexiPart::Item *item, const QString& newName) { Q_ASSERT(item); KDbConnection *conn = KexiMainWindowIface::global()->project()->dbConnection(); KDbTableSchema *schema = conn->tableSchema(item->identifier()); if (!schema) return false; const tristate res = KexiTablePart::askForClosingObjectsUsingTableSchema( KexiMainWindowIface::global()->openedWindowFor(item->identifier()), conn, schema, - xi18n("You are about to rename table %1 but following objects using this table are opened:", - schema->name())); + kxi18n("You are about to rename table %1 but it is used by " + "following opened windows:") + .subs(schema->name())); if (res != true) { return res; } return conn->alterTableName(schema, newName); } KDbObject* KexiTablePart::loadSchemaObject(KexiWindow *window, const KDbObject& object, Kexi::ViewMode viewMode, bool *ownedByWindow) { Q_UNUSED(window); Q_UNUSED(viewMode); if (ownedByWindow) *ownedByWindow = false; return KexiMainWindowIface::global()->project()->dbConnection()->tableSchema(object.name()); } -//static -tristate KexiTablePart::askForClosingObjectsUsingTableSchema( - KexiWindow *window, KDbConnection *conn, - KDbTableSchema *table, const QString& msg) +// static +tristate KexiTablePart::askForClosingObjectsUsingTableSchema(KexiWindow *window, + KDbConnection *conn, + KDbTableSchema *table, + const KLocalizedString &msg) { Q_ASSERT(conn); Q_ASSERT(table); if (!window) { return true; } QList listeners = KDbTableSchemaChangeListener::listeners(conn, table); KexiTablePartTempData *temp = static_cast(window->data()); // Special case: listener that is equal to window->data() will be silently closed // without asking for confirmation. It is not counted when looking for objects that // are "blocking" changes of the table. const bool tempListenerExists = listeners.removeAll(temp) > 0; // Immediate success if there's no temp-data's listener to close nor other listeners to close if (!tempListenerExists && listeners.isEmpty()) { return true; } if (!listeners.isEmpty()) { - QString openedObjectsStr = ""; + QString openedObjectsStr = "

    "; for(const KDbTableSchemaChangeListener* listener : listeners) { - openedObjectsStr += QString("%1").arg(listener->name()); + openedObjectsStr += QString("
  • %1
  • ").arg(listener->name()); } - openedObjectsStr += ""; - const int r = KMessageBox::questionYesNo(window, - i18nc("@info", "%1%2", msg, openedObjectsStr) - + "" - + xi18n("Do you want to close all windows for these objects?") - + "", - QString(), KGuiItem(xi18nc("@action:button Close All Windows", "Close Windows"), koIconName("window-close")), KStandardGuiItem::cancel()); + openedObjectsStr += "

"; + QString message = "" + + i18nc("@info/plain Sentence1 Sentence2 Sentence3", "%1%2%3", + KexiUtils::localizedStringToHtmlSubstring(msg), openedObjectsStr, + KexiUtils::localizedStringToHtmlSubstring( + kxi18nc("@info", "Do you want to close these windows and save the " + "design or cancel saving?"))) + + ""; + KGuiItem closeAndSaveItem(KStandardGuiItem::save()); + closeAndSaveItem.setText( + xi18nc("@action:button Close all windows and save", "Close Windows and Save")); + closeAndSaveItem.setToolTip(xi18nc("@info:tooltip Close all windows and save design", + "Close all windows and save design")); + const int r = KMessageBox::questionYesNo(window, message, QString(), closeAndSaveItem, + KStandardGuiItem::cancel(), QString(), + KMessageBox::Notify | KMessageBox::Dangerous); if (r != KMessageBox::Yes) { return cancelled; } } - //try to close every window depending on the table (if present) and also the temp-data's listener (if present) - const tristate res = KDbTableSchemaChangeListener::closeListeners(conn, table); + // try to close every window depending on the table (if present) and also the temp-data's + // listener (if present) + const tristate res = KDbTableSchemaChangeListener::closeListeners(conn, table, { temp }); if (res != true) { //do not expose closing errors twice; just cancel return cancelled; } return true; } KLocalizedString KexiTablePart::i18nMessage( const QString& englishMessage, KexiWindow* window) const { Q_UNUSED(window); if (englishMessage == "Design of object %1 has been modified.") return kxi18nc(I18NC_NOOP("@info", "Design of table %1 has been modified.")); if (englishMessage == "Object %1 already exists.") return kxi18nc(I18NC_NOOP("@info", "Table %1 already exists.")); if (window->currentViewMode() == Kexi::DesignViewMode && !window->neverSaved() && englishMessage == ":additional message before saving design") return kxi18nc(I18NC_NOOP("@info", "Any data in this table will be deleted upon design's saving!")); return Part::i18nMessage(englishMessage, window); } void KexiTablePart::setupCustomPropertyPanelTabs(QTabWidget *tab) { if (!d->lookupColumnPage) { d->lookupColumnPage = new KexiLookupColumnPage(0); connect(d->lookupColumnPage, SIGNAL(jumpToObjectRequested(QString,QString)), KexiMainWindowIface::global()->thisWidget(), SLOT(highlightObject(QString,QString))); //! @todo add "Table" tab /* connect(d->dataSourcePage, SIGNAL(formDataSourceChanged(QCString,QCString)), KFormDesigner::FormManager::self(), SLOT(setFormDataSource(QCString,QCString))); connect(d->dataSourcePage, SIGNAL(dataSourceFieldOrExpressionChanged(QString,QString,KDbField::Type)), KFormDesigner::FormManager::self(), SLOT(setDataSourceFieldOrExpression(QString,QString,KDbField::Type))); connect(d->dataSourcePage, SIGNAL(insertAutoFields(QString,QString,QStringList)), KFormDesigner::FormManager::self(), SLOT(insertAutoFields(QString,QString,QStringList)));*/ } KexiProject *prj = KexiMainWindowIface::global()->project(); d->lookupColumnPage->setProject(prj); //! @todo add lookup field icon tab->addTab(d->lookupColumnPage, KexiIcon("combobox"), QString()); tab->setTabToolTip(tab->indexOf(d->lookupColumnPage), xi18n("Lookup column")); } KexiLookupColumnPage* KexiTablePart::lookupColumnPage() const { return d->lookupColumnPage; } //---------------- class Q_DECL_HIDDEN KexiTablePartTempData::Private { public: Private() : table(nullptr) { } KDbTableSchema *table; KDbConnection *conn; }; KexiTablePartTempData::KexiTablePartTempData(KexiWindow* parent, KDbConnection *conn) : KexiWindowData(parent) , KDbTableSchemaChangeListener() , tableSchemaChangedInPreviousView(true /*to force reloading on startup*/) , d(new Private) { d->conn = conn; setName(KexiUtils::localizedStringToHtmlSubstring( kxi18nc("@info", "Table %1").subs(parent->partItem()->name()))); } KexiTablePartTempData::~KexiTablePartTempData() { KDbTableSchemaChangeListener::unregisterForChanges(d->conn, this); delete d; } KDbTableSchema* KexiTablePartTempData::table() { return d->table; } KDbConnection* KexiTablePartTempData::connection() { return d->conn; } +void KexiTablePartTempData::closeDataInDataView() +{ + const KexiWindow* window = static_cast(parent()); + if (window->currentViewMode() != Kexi::DataViewMode) { + KexiTableDesigner_DataView *dataView + = qobject_cast(window->viewForMode(Kexi::DataViewMode)); + if (dataView && dataView->tableView()->data()) { + dataView->setData(nullptr); + } + } +} + void KexiTablePartTempData::setTable(KDbTableSchema *table) { if (d->table == table) { return; } if (d->table) { - KDbTableSchemaChangeListener::unregisterForChanges(d->conn, this, d->table); + KDbTableSchemaChangeListener::unregisterForChanges(d->conn, d->table); } + closeDataInDataView(); d->table = table; if (d->table) { KDbTableSchemaChangeListener::registerForChanges(d->conn, this, d->table); } } tristate KexiTablePartTempData::closeListener() { KexiWindow* window = static_cast(parent()); - if (window->currentViewMode() != Kexi::DataViewMode) { - KexiTableDesigner_DataView *dataView - = qobject_cast(window->viewForMode(Kexi::DataViewMode)); - if (dataView && dataView->tableView()->data()) { - dataView->setData(nullptr); - } + qDebug() << window->partItem()->name(); + closeDataInDataView(); + if (closeWindowOnCloseListener) { + return KexiMainWindowIface::global()->closeWindow(window); } return true; } #include "kexitablepart.moc" diff --git a/src/plugins/tables/kexitablepart.h b/src/plugins/tables/kexitablepart.h index 5f660c226..267db835d 100644 --- a/src/plugins/tables/kexitablepart.h +++ b/src/plugins/tables/kexitablepart.h @@ -1,128 +1,147 @@ /* This file is part of the KDE project Copyright (C) 2003 Lucijan Busch Copyright (C) 2002, 2003 Joseph Wenninger Copyright (C) 2004-2017 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 KEXITABLEPART_H #define KEXITABLEPART_H #include #include #include #include #include #include class KexiLookupColumnPage; //! @short Temporary data kept in memory while switching between Table Window's views class KexiTablePartTempData : public KexiWindowData, public KDbTableSchemaChangeListener { Q_OBJECT public: KexiTablePartTempData(KexiWindow* parent, KDbConnection *conn); ~KexiTablePartTempData(); //! Table used for this data KDbTableSchema* table(); //! Sets table used for this data //! If the previous table differs from @a table and is not @c nullptr, listener for //! it will be unregistered. //! If @a table is not @c nullptr, this temp-data object will be registered as a listener //! for it. void setTable(KDbTableSchema *table); //! Connection used for retrieving definition of the query KDbConnection* connection(); /*! true, if \a table member has changed in previous view. Used on view switching. We're checking this flag to see if we should refresh data for DataViewMode. */ bool tableSchemaChangedInPreviousView; + //! @c true indicates that closeListener() should close the table designer window. + //! This is disabled in one case: upon saving of the design of this table. + //! @see KexiTableDesignerView::storeData() + bool closeWindowOnCloseListener = true; + protected: //! Closes listener - this temp-data acts as a listener for tracking changes in table schema //! that is displayed in the window's data view. //! It just calls KexiDataTableView::setData(nullptr) is there's data set for the view //! (i.e. if KexiDataTableView::tableView()->data() is not @c nullptr). tristate closeListener() override; private: + void closeDataInDataView(); + Q_DISABLE_COPY(KexiTablePartTempData) class Private; Private * const d; }; //! @short Kexi Table Designer plugin class KexiTablePart : public KexiPart::Part { Q_OBJECT public: KexiTablePart(QObject *parent, const QVariantList &); virtual ~KexiTablePart(); virtual tristate remove(KexiPart::Item *item); virtual tristate rename(KexiPart::Item *item, const QString& newName); - //! Close objects that listenen to changes of the table schema @a table. - //! Asks the user for approval if there is at least one object that listens for changes - //! of the schema. If there is no approval, returns @c cancelled. - //! On failure returns @c false. - //! If @a window is @c nullptr, @c true is returned immediately because there is no window to - //! care about. - //! Special case: listener that is equal to window->data() will be silently closed - //! without asking for confirmation. It is not counted when looking for objects that - //! are "blocking" changes of @a table. - //! This exception is needed because the listener handles the data view's lifetime - //! and the data view should be reset silently without bothering the user. - //! See KexiTablePartTempData::closeListener() - static tristate askForClosingObjectsUsingTableSchema( - KexiWindow *window, KDbConnection *conn, - KDbTableSchema *table, const QString& msg); + /** + * Closes objects that listenen to changes of the table schema @a table, i.e. use it. + * + * These objects can be currently: + * - lookup fields of other tables + * - queries using the table directly or via lookup fields + * - forms and reports that use the table directly as data source or via query. + * + * Scripts referencing the table programatically are not analyzed, so they can fail on next + * execution. + * + * This method asks the user for approval if there is at least one object that listens for + * changes of the schema (altering, renaming or removal). If there is no approval, returns + * @c cancelled. On failure @c false is returned. If @a window is @c nullptr, @c true is + * returned immediately because there is no window to care about. + * + * Special case: listener for the table @a table will be silently closed without asking for + * confirmation. It is ignored when looking for objects that are "blocking" changes + * of @a table. This exception is needed because the listener handles the data view's lifetime + * and the data view should be reset silently without bothering the user. + * + * @see KexiTablePartTempData::closeListener() + * @see KexiQueryPart::askForClosingObjectsUsingQuerySchema() + */ + static tristate askForClosingObjectsUsingTableSchema(KexiWindow *window, KDbConnection *conn, + KDbTableSchema *table, + const KLocalizedString &msg); virtual KLocalizedString i18nMessage(const QString& englishMessage, KexiWindow* window) const; KexiLookupColumnPage* lookupColumnPage() const; protected: KexiWindowData* createWindowData(KexiWindow* window) override Q_REQUIRED_RESULT; KexiView *createView(QWidget *parent, KexiWindow *window, KexiPart::Item *item, Kexi::ViewMode viewMode = Kexi::DataViewMode, QMap *staticObjectArgs = nullptr) override Q_REQUIRED_RESULT; virtual void initPartActions(); virtual void initInstanceActions(); virtual void setupCustomPropertyPanelTabs(QTabWidget *tab); virtual KDbObject* loadSchemaObject(KexiWindow *window, const KDbObject& object, Kexi::ViewMode viewMode, bool *ownedByWindow); private: class Private; Private* const d; }; #endif diff --git a/src/widget/dataviewcommon/kexiformdataiteminterface.h b/src/widget/dataviewcommon/kexiformdataiteminterface.h index ab336f898..a188c3a45 100644 --- a/src/widget/dataviewcommon/kexiformdataiteminterface.h +++ b/src/widget/dataviewcommon/kexiformdataiteminterface.h @@ -1,172 +1,172 @@ /* This file is part of the KDE project Copyright (C) 2005-2009 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 KEXIFORMDATAITEMINTERFACE_H #define KEXIFORMDATAITEMINTERFACE_H #include "kexidataviewcommon_export.h" #include #include #include class KDbConnection; class KDbField; //! An interface for declaring form widgets to be data-aware. class KEXIDATAVIEWCOMMON_EXPORT KexiFormDataItemInterface : public KexiDataItemInterface { public: KexiFormDataItemInterface(); virtual ~KexiFormDataItemInterface(); //! \return the name of the data source for this widget. //! Data source usually means here a table or query, a field name or an expression. inline QString dataSource() const { return m_dataSource; } //! Sets the name of the data source for this widget. //! Data source usually means here a table or query or field name name. inline void setDataSource(const QString &ds) { m_dataSource = ds; } /*! \return the plugin ID of the part for the widget's data source. It means IDs like "org.kexi-project.table" or "org.kexi-project.query" if the data source is set to object (as within form) or is empty if the data source is set to table field or query column. */ inline QString dataSourcePluginId() const { return m_dataSourcePartClass; } /*! Sets the plugin ID of the part for the data widget's data source. It usually means here a "org.kexi-project.table" or "org.kexi-project.query". @see dataSourcePluginId() */ inline void setDataSourcePluginId(const QString &pluginId) { m_dataSourcePartClass = pluginId; } /*! If \a displayDefaultValue is true, the value set by KexiDataItemInterface::setValue() is displayed in a special way. Used by KexiFormDataProvider::fillDataItems(). \a widget is equal to 'this'. You can reimplement this in the widget. Always call the superclass' implementation. setDisplayDefaultValue(.., false) is called in KexiFormScrollView::valueChanged() as a response on data change performed by user. */ virtual void setDisplayDefaultValue(QWidget* widget, bool displayDefaultValue); /*! \return true if default value is displayed for this item. */ virtual bool hasDisplayedDefaultValue() const { return m_displayDefaultValue; } /*! Convenience function: casts this item to a QWidget. Can return 0 if the item is not a QWidget-derived object. */ virtual QWidget* widget() { return dynamic_cast(this); } /*! Sets 'invalid' state, e.g. a text editor widget should display text \a displayText and become read only to prevent entering data, because updating at the database backend is not available. \a displayText is usually set to something i18n'd like "#NAME?". Note: that even widgets that usually do not display texts (e.g. pixmaps) should display \a displayText too. */ virtual void setInvalidState(const QString& displayText) = 0; /*! Changes 'read only' flag, for this widget. Typically this flag can be passed to a widget itself, e.g. QLineEdit::setReadOnly(bool). */ virtual void setReadOnly(bool readOnly) = 0; //! \return database column information for this item KDbField* field() override; //! \return database column information for this item KDbQueryColumnInfo* columnInfo() { return m_columnInfo; } /*! Used internally to set database column information. Reimplement if you need to do additional actions, e.g. set data validator based on field type. Don't forget about calling superclass implementation. */ virtual void setColumnInfo(KDbConnection *conn, KDbQueryColumnInfo* cinfo) { Q_UNUSED(conn) m_columnInfo = cinfo; } /*! Used internally to set visible database column information. Reimplemented in KexiDBComboBox: except for combo box, this does nothing. */ virtual void setVisibleColumnInfo(KDbQueryColumnInfo* cinfo) { Q_UNUSED(cinfo); } /*! \return visible database column information for this item. Except for combo box, this is exactly the same as columnInfo(). */ virtual KDbQueryColumnInfo* visibleColumnInfo() { return columnInfo(); } /*! Does nothing, because within forms, widgets are always visible. */ virtual void hideWidget() { } /*! Does nothing, because within forms, widgets are always visible. */ virtual void showWidget() { } /*! Undoes changes made to this item - just resets the widget to original value. Note: This is internal method called by KexiFormScrollView::cancelEditor(). To cancel editing of the widget's data from the widget's code, use KexiFormDataItemInterface::cancelEditor(). Reimplemented in KexiDBComboBox to also revert the visible value (i.e. text) to the original state. */ virtual void undoChanges(); /* Cancels editing of the widget's data. This method just looks for the (grand)parent KexiFormScrollView object and calls KexiFormScrollView::cancelEditor(). */ void cancelEditor(); /*! @internal Called by top-level form on key press event. Default implementation does nothing. Implement this if you want to handle key presses from within the editor widget item. \return true if \a ke should be accepted by the widget item. This method is used e.g. in KexiDBImageBox for Key_Escape to if the popup is visible, so the key press won't be consumed to perform "cancel editing". */ virtual bool keyPressed(QKeyEvent *ke) { Q_UNUSED(ke); return false; } //! Selects contents of the widget if there is such behaviour set (it is by default). //! @todo add option for not selecting the field virtual void selectAllOnFocusIfNeeded(); protected: QString m_dataSource; - QString m_dataSourcePartClass; + QString m_dataSourcePartClass = QStringLiteral("org.kexi-project.table"); //!< default for cases when the part class is missing KDbQueryColumnInfo* m_columnInfo; KexiDisplayUtils::DisplayParameters *m_displayParametersForEnteredValue; //!< used in setDisplayDefaultValue() KexiDisplayUtils::DisplayParameters *m_displayParametersForDefaultValue; //!< used in setDisplayDefaultValue() bool m_displayDefaultValue; //!< used by setDisplayDefaultValue() friend class KexiDBAutoField; }; #endif