1 /* This file is part of the KDE project
2    Copyright (C) 2005-2017 Jarosław Staniek <staniek@kde.org>
3 
4    This library is free software; you can redistribute it and/or
5    modify it under the terms of the GNU Library General Public
6    License as published by the Free Software Foundation; either
7    version 2 of the License, or (at your option) any later version.
8 
9    This library is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12    Library General Public License for more details.
13 
14    You should have received a copy of the GNU Library General Public License
15    along with this library; see the file COPYING.LIB.  If not, write to
16    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18 */
19 
20 #include "kexidatasourcepage.h"
21 #include <KexiIcon.h>
22 #include <config-kexi.h>
23 #include <widget/properties/KexiPropertyEditorView.h>
24 #include <widget/KexiObjectInfoLabel.h>
25 #include <widget/KexiDataSourceComboBox.h>
26 #include <widget/fields/KexiFieldListView.h>
27 #include <widget/fields/KexiFieldComboBox.h>
28 #include <kexiutils/SmallToolButton.h>
29 #include <kexiutils/KexiFadeWidgetEffect.h>
30 #include <kexiutils/utils.h>
31 #include <kexiproject.h>
32 #include <formeditor/commands.h>
33 
34 #include <KDbConnection>
35 
36 #include <KProperty>
37 
38 #include <KLocalizedString>
39 
40 #include <QLabel>
41 #include <QLineEdit>
42 #include <QHBoxLayout>
43 
KexiDataSourcePage(QWidget * parent)44 KexiDataSourcePage::KexiDataSourcePage(QWidget *parent)
45         : KexiPropertyPaneViewBase(parent)
46         , m_noDataSourceAvailableSingleText(
47             xi18n("No data source could be assigned for this widget.") )
48         , m_noDataSourceAvailableMultiText(
49             xi18n("No data source could be assigned for multiple widgets.") )
50         , m_insideClearFormDataSourceSelection(false)
51 #ifndef KEXI_AUTOFIELD_FORM_WIDGET_SUPPORT
52         , m_tableOrQuerySchema(0)
53 #endif
54 {
55     infoLabel()->setContentsMargins(0, 0, 0, spacing());
56 
57     m_noDataSourceAvailableLabel = new QLabel(m_noDataSourceAvailableSingleText, this);
58     m_noDataSourceAvailableLabel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum);
59     m_noDataSourceAvailableLabel->setContentsMargins(0, 0, 0, spacing());
60     m_noDataSourceAvailableLabel->setAlignment(Qt::AlignBottom | Qt::AlignLeft);
61     m_noDataSourceAvailableLabel->setWordWrap(true);
62     mainLayout()->addWidget(m_noDataSourceAvailableLabel);
63 
64     //-Widget's Data Source
65     QHBoxLayout *hlyr = new QHBoxLayout();
66     mainLayout()->addLayout(hlyr);
67 #if 0
68 //! @todo unhide this when expression work
69 // m_widgetDSLabel = new QLabel(futureI18nc("Table Field, Query Field or Expression", "Source field or expression"), this);
70 #else
71     m_widgetDSLabel = new QLabel(
72         xi18nc("Table Field or Query Field", "Widget's data source:"), this);
73 #endif
74     m_widgetDSLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
75     m_widgetDSLabel->setAlignment(Qt::AlignLeft | Qt::AlignBottom);
76     hlyr->addWidget(m_widgetDSLabel);
77     mainLayout()->addSpacing(KexiUtils::spacingHint()); // needed because unlike m_dataSourceLabel we have no button in hlyr
78 
79 #if 0
80     m_clearWidgetDSButton = new KexiSmallToolButton(
81         koIcon("edit-clear-locationbar-rtl"), QString(), this);
82     m_clearWidgetDSButton->setObjectName("clearWidgetDSButton");
83     m_clearWidgetDSButton->setMinimumHeight(m_widgetDSLabel->minimumHeight());
84     m_clearWidgetDSButton->setToolTip(futureI18n("Clear widget's data source"));
85     hlyr->addWidget(m_clearWidgetDSButton);
86     connect(m_clearWidgetDSButton, SIGNAL(clicked()),
87             this, SLOT(clearWidgetDataSourceSelection()));
88 #endif
89 
90     m_widgetDataSourceCombo = new KexiFieldComboBox(this);
91     m_widgetDataSourceCombo->setObjectName("sourceFieldCombo");
92     m_widgetDataSourceCombo->setContentsMargins(0, 0, 0, 0);
93     m_widgetDSLabel->setBuddy(m_widgetDataSourceCombo);
94     connect(m_widgetDataSourceCombo, SIGNAL(editTextChanged(QString)),
95         this, SLOT(slotWidgetDataSourceTextChanged(QString)));
96     mainLayout()->addWidget(m_widgetDataSourceCombo);
97 
98     m_widgetDataSourceComboSpacer = addWidgetSpacer();
99 
100     //- Form's Data Source
101     hlyr = new QHBoxLayout();
102     hlyr->setContentsMargins(0, 0, 0, 0);
103     mainLayout()->addLayout(hlyr);
104     m_dataSourceLabel = new QLabel(xi18n("Form's data source:"), this);
105     m_dataSourceLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
106     m_dataSourceLabel->setAlignment(Qt::AlignLeft | Qt::AlignBottom);
107     hlyr->addWidget(m_dataSourceLabel);
108 
109     m_gotoButton = new KexiSmallToolButton(
110         koIcon("go-jump"), QString(), this);
111     m_gotoButton->setObjectName("gotoButton");
112     m_gotoButton->setToolTip(xi18n("Go to selected form's data source"));
113     m_gotoButton->setWhatsThis(xi18n("Goes to selected form's data source"));
114     hlyr->addWidget(m_gotoButton);
115     connect(m_gotoButton, SIGNAL(clicked()), this, SLOT(slotGotoSelected()));
116 
117 #if 0
118     m_clearDSButton = new KexiSmallToolButton(
119         koIcon("edit-clear-locationbar-rtl"), QString(), this);
120     m_clearDSButton->setObjectName("clearDSButton");
121     m_clearDSButton->setMinimumHeight(m_dataSourceLabel->minimumHeight());
122     m_clearDSButton->setToolTip(futureI18n("Clear form's data source"));
123     hlyr->addWidget(m_clearDSButton);
124     connect(m_clearDSButton, SIGNAL(clicked()), this, SLOT(clearFormDataSourceSelection()));
125 #endif
126 
127     m_formDataSourceCombo = new KexiDataSourceComboBox(this);
128     m_formDataSourceCombo->setObjectName("dataSourceCombo");
129     m_formDataSourceCombo->setContentsMargins(0, 0, 0, 0);
130     m_dataSourceLabel->setBuddy(m_formDataSourceCombo);
131     mainLayout()->addWidget(m_formDataSourceCombo);
132 
133     m_formDataSourceComboSpacer = addWidgetSpacer();
134 
135 #ifndef KEXI_AUTOFIELD_FORM_WIDGET_SUPPORT
136     mainLayout()->addStretch();
137 #else
138     //2. Inserting fields
139 
140     //helper info
141 //! @todo allow to hide such helpers by adding global option
142     hlyr = new QHBoxLayout();
143     hlyr->setContentsMargins(0, 0, 0, 0);
144     mainLayout()->addLayout(hlyr);
145     m_mousePointerLabel = new QLabel(this);
146     hlyr->addWidget(m_mousePointerLabel);
147     m_mousePointerLabel->setPixmap(koIcon("tool-pointer"));
148     m_mousePointerLabel->setFixedWidth(m_mousePointerLabel->pixmap()
149                                        ? m_mousePointerLabel->pixmap()->width() : 0);
150     m_availableFieldsDescriptionLabel = new QLabel(
151         futureI18n("Select fields from the list below and drag them onto"
152              " a form or click the <interface>Insert</interface> button"), this);
153     m_availableFieldsDescriptionLabel->setAlignment(Qt::AlignLeft);
154     m_availableFieldsDescriptionLabel->setWordWrap(true);
155     hlyr->addWidget(m_availableFieldsDescriptionLabel);
156 
157     //Available Fields
158     hlyr = new QHBoxLayout();
159     hlyr->setContentsMargins(0, 0, 0, 0);
160     mainLayout()->addLayout(hlyr);
161     m_availableFieldsLabel = new QLabel(futureI18n("Available fields"), this);
162     m_availableFieldsLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
163     hlyr->addWidget(m_availableFieldsLabel);
164 
165     m_addField = new KexiSmallToolButton(
166         KexiIcon("add-field"), futureI18nc("Insert selected field into form", "Insert"), this);
167     m_addField->setObjectName("addFieldButton");
168     m_addField->setFocusPolicy(Qt::StrongFocus);
169     m_addField->setToolTip(futureI18n("Insert selected fields into form"));
170     m_addField->setWhatsThis(futureI18n("Inserts selected fields into form"));
171     hlyr->addWidget(m_addField);
172     connect(m_addField, SIGNAL(clicked()), this, SLOT(slotInsertSelectedFields()));
173 
174     m_fieldListView = new KexiFieldListView(this,
175         KexiFieldListView::ShowDataTypes | KexiFieldListView::AllowMultiSelection);
176     m_fieldListView->setObjectName("fieldListView");
177     m_fieldListView->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding));
178     m_availableFieldsLabel->setBuddy(m_fieldListView);
179     mainLayout()->addWidget(m_fieldListView, 1);
180     connect(m_fieldListView, SIGNAL(selectionChanged()),
181             this, SLOT(slotFieldListViewSelectionChanged()));
182     connect(m_fieldListView,
183             SIGNAL(fieldDoubleClicked(QString,QString,QString)),
184             this, SLOT(slotFieldDoubleClicked(QString,QString,QString)));
185 #endif
186 
187     mainLayout()->addStretch(1);
188 
189     connect(m_formDataSourceCombo, SIGNAL(editTextChanged(QString)),
190             this, SLOT(slotFormDataSourceTextChanged(QString)));
191     connect(m_formDataSourceCombo, SIGNAL(dataSourceChanged()),
192             this, SLOT(slotFormDataSourceChanged()));
193     connect(m_widgetDataSourceCombo, SIGNAL(selected()),
194             this, SLOT(slotFieldSelected()));
195 
196     clearFormDataSourceSelection();
197     slotFieldListViewSelectionChanged();
198 }
199 
~KexiDataSourcePage()200 KexiDataSourcePage::~KexiDataSourcePage()
201 {
202 #ifndef KEXI_AUTOFIELD_FORM_WIDGET_SUPPORT
203     delete m_tableOrQuerySchema;
204 #endif
205 }
206 
setProject(KexiProject * prj)207 void KexiDataSourcePage::setProject(KexiProject *prj)
208 {
209     m_widgetDataSourceCombo->setProject(prj);
210     m_formDataSourceCombo->setProject(prj);
211 }
212 
clearFormDataSourceSelection(bool alsoClearComboBox)213 void KexiDataSourcePage::clearFormDataSourceSelection(bool alsoClearComboBox)
214 {
215     if (m_insideClearFormDataSourceSelection)
216         return;
217     m_insideClearFormDataSourceSelection = true;
218     if (alsoClearComboBox && !m_formDataSourceCombo->selectedName().isEmpty())
219         m_formDataSourceCombo->setDataSource(QString(), QString());
220     m_gotoButton->setEnabled(false);
221     m_widgetDataSourceCombo->setFieldOrExpression(QString());
222 #ifdef KEXI_AUTOFIELD_FORM_WIDGET_SUPPORT
223     m_addField->setEnabled(false);
224     m_fieldListView->clear();
225 #endif
226     m_insideClearFormDataSourceSelection = false;
227 }
228 
slotWidgetDataSourceTextChanged(const QString & text)229 void KexiDataSourcePage::slotWidgetDataSourceTextChanged(const QString &text)
230 {
231     if (text.isEmpty()) {
232         clearWidgetDataSourceSelection();
233     }
234 }
235 
clearWidgetDataSourceSelection()236 void KexiDataSourcePage::clearWidgetDataSourceSelection()
237 {
238     m_widgetDataSourceCombo->setFieldOrExpression(QString());
239     slotFieldSelected();
240 }
241 
slotGotoSelected()242 void KexiDataSourcePage::slotGotoSelected()
243 {
244     const QString pluginId(m_formDataSourceCombo->selectedPluginId());
245     bool ok;
246     (void)KexiProject::pluginIdToTableOrQueryType(pluginId, &ok);
247     if (ok) {
248         if (m_formDataSourceCombo->isSelectionValid())
249             emit jumpToObjectRequested(pluginId, m_formDataSourceCombo->selectedName());
250     }
251 }
252 
slotInsertSelectedFields()253 void KexiDataSourcePage::slotInsertSelectedFields()
254 {
255 #ifdef KEXI_AUTOFIELD_FORM_WIDGET_SUPPORT
256     QStringList selectedFieldNames(m_fieldListView->selectedFieldNames());
257     if (selectedFieldNames.isEmpty())
258         return;
259 
260     emit insertAutoFields(m_fieldListView->schema()->table()
261                             ? "org.kexi-project.table" : "org.kexi-project.query",
262                           m_fieldListView->schema()->name(), selectedFieldNames);
263 #endif
264 }
265 
slotFieldDoubleClicked(const QString & sourcePluginId,const QString & sourceName,const QString & fieldName)266 void KexiDataSourcePage::slotFieldDoubleClicked(const QString& sourcePluginId, const QString& sourceName,
267         const QString& fieldName)
268 {
269 #ifdef KEXI_AUTOFIELD_FORM_WIDGET_SUPPORT
270     QStringList selectedFields;
271     selectedFields.append(fieldName);
272     emit insertAutoFields(sourcePluginId, sourceName, selectedFields);
273 #else
274     Q_UNUSED(sourcePluginId);
275     Q_UNUSED(sourceName);
276     Q_UNUSED(fieldName);
277 #endif
278 }
279 
slotFormDataSourceTextChanged(const QString & text)280 void KexiDataSourcePage::slotFormDataSourceTextChanged(const QString &text)
281 {
282     const bool enable = m_formDataSourceCombo->isSelectionValid();
283     if (text.isEmpty()) {
284         clearFormDataSourceSelection();
285     } else if (!enable) {
286         clearFormDataSourceSelection(m_formDataSourceCombo->selectedName().isEmpty()/*alsoClearComboBox*/);
287     }
288     updateSourceFieldWidgetsAvailability();
289 }
290 
slotFormDataSourceChanged()291 void KexiDataSourcePage::slotFormDataSourceChanged()
292 {
293     if (!m_formDataSourceCombo->project())
294         return;
295     const QString pluginId(m_formDataSourceCombo->selectedPluginId());
296     bool dataSourceFound = false;
297     QString name(m_formDataSourceCombo->selectedName());
298     bool isIdAcceptable;
299     const KDbTableOrQuerySchema::Type type = KexiProject::pluginIdToTableOrQueryType(
300                 pluginId, &isIdAcceptable);
301     if (isIdAcceptable && m_formDataSourceCombo->isSelectionValid()) {
302         KDbTableOrQuerySchema *tableOrQuery = new KDbTableOrQuerySchema(
303             m_formDataSourceCombo->project()->dbConnection(), name.toLatin1(), type);
304         if (tableOrQuery->table() || tableOrQuery->query()) {
305 #ifdef KEXI_AUTOFIELD_FORM_WIDGET_SUPPORT
306             m_fieldListView->setSchema(tableOrQuery);
307 #else
308             m_tableOrQuerySchema = tableOrQuery;
309 #endif
310             dataSourceFound = true;
311             m_widgetDataSourceCombo->setTableOrQuery(name, type);
312         } else {
313             delete tableOrQuery;
314         }
315     }
316     if (!dataSourceFound) {
317         m_widgetDataSourceCombo->setTableOrQuery(QString(), KDbTableOrQuerySchema::Type::Table);
318     }
319     m_gotoButton->setEnabled(dataSourceFound);
320     if (dataSourceFound) {
321         slotFieldListViewSelectionChanged();
322     } else {
323 #ifdef KEXI_AUTOFIELD_FORM_WIDGET_SUPPORT
324         m_addField->setEnabled(false);
325 #endif
326     }
327     updateSourceFieldWidgetsAvailability();
328     emit formDataSourceChanged(pluginId, name);
329 }
330 
slotFieldSelected()331 void KexiDataSourcePage::slotFieldSelected()
332 {
333     KDbField::Type dataType = KDbField::InvalidType;
334 #ifdef KEXI_AUTOFIELD_FORM_WIDGET_SUPPORT
335     //! @todo this should also work for expressions
336         KDbField *field = m_fieldListView->schema()->field(
337                                    m_widgetDataSourceCombo->fieldOrExpression());
338 #else
339     KDbField *field = m_tableOrQuerySchema->field(
340                                m_widgetDataSourceCombo->fieldOrExpression());  //temp
341 #endif
342     if (field)
343         dataType = field->type();
344 
345     emit dataSourceFieldOrExpressionChanged(
346         m_widgetDataSourceCombo->fieldOrExpression(),
347         m_widgetDataSourceCombo->fieldOrExpressionCaption(),
348         dataType
349     );
350 }
351 
setFormDataSource(const QString & pluginId,const QString & name)352 void KexiDataSourcePage::setFormDataSource(const QString& pluginId, const QString& name)
353 {
354     m_formDataSourceCombo->setDataSource(pluginId, name);
355 }
356 
357 #define KexiDataSourcePage_FADE 1
358 
assignPropertySet(KPropertySet * propertySet)359 void KexiDataSourcePage::assignPropertySet(KPropertySet* propertySet)
360 {
361     QString objectName;
362     if (propertySet)
363         objectName = propertySet->propertyValue("objectName").toString();
364     if (!objectName.isEmpty() && objectName == m_currentObjectName)
365         return; //the same object
366     m_currentObjectName = objectName;
367 
368 //! @todo
369 #if KexiDataSourcePage_FADE
370     KexiFadeWidgetEffect *animation = 0;
371     if (isVisible())
372         animation = new KexiFadeWidgetEffect(this);
373 #endif
374     QString objectClassName;
375     if (propertySet) {
376         objectClassName = propertySet->propertyValue("this:className").toString();
377     }
378     updateInfoLabelForPropertySet(propertySet);
379 
380     const bool isForm = objectClassName == "KexiDBForm";
381     const bool multipleSelection = objectClassName == "special:multiple";
382     const bool hasDataSourceProperty = propertySet
383                                        && propertySet->contains("dataSource") && !multipleSelection;
384 
385     if (!isForm) {
386         //this is a widget
387         QString dataSource;
388         if (hasDataSourceProperty) {
389             if (propertySet) {
390                 dataSource = (*propertySet)["dataSource"].value().toString();
391             }
392             m_noDataSourceAvailableLabel->hide();
393             m_widgetDataSourceCombo->setFieldOrExpression(dataSource);
394             m_widgetDataSourceCombo->setEnabled(true);
395             m_widgetDSLabel->show();
396             m_widgetDataSourceCombo->show();
397             m_widgetDataSourceComboSpacer->show();
398             updateSourceFieldWidgetsAvailability();
399         }
400     }
401 
402     if (isForm) {
403         m_noDataSourceAvailableLabel->hide();
404     }
405     else if (!hasDataSourceProperty) {
406         if (multipleSelection) {
407             m_noDataSourceAvailableLabel->setText(m_noDataSourceAvailableMultiText);
408         }
409         else {
410             m_noDataSourceAvailableLabel->setText(m_noDataSourceAvailableSingleText);
411         }
412         m_noDataSourceAvailableLabel->show();
413         m_widgetDataSourceCombo->setEditText(QString());
414     }
415 
416     if (isForm || !hasDataSourceProperty) {
417         //no source field can be set
418         m_widgetDSLabel->hide();
419         m_widgetDataSourceCombo->hide();
420         m_widgetDataSourceComboSpacer->hide();
421     }
422 //! @todo
423 #if KexiDataSourcePage_FADE
424     if (animation)
425         animation->start(100);
426 #endif
427 }
428 
slotFieldListViewSelectionChanged()429 void KexiDataSourcePage::slotFieldListViewSelectionChanged()
430 {
431 #ifdef KEXI_AUTOFIELD_FORM_WIDGET_SUPPORT
432     //update "add field" button's state
433     for (Q3ListViewItemIterator it(m_fieldListView); it.current(); ++it) {
434         if (it.current()->isSelected()) {
435             m_addField->setEnabled(true);
436             return;
437         }
438     }
439     m_addField->setEnabled(false);
440 #endif
441 }
442 
updateSourceFieldWidgetsAvailability()443 void KexiDataSourcePage::updateSourceFieldWidgetsAvailability()
444 {
445     const bool hasDataSource = m_formDataSourceCombo->isSelectionValid();
446     m_widgetDataSourceCombo->setEnabled(hasDataSource);
447     m_widgetDSLabel->setEnabled(hasDataSource);
448 #ifdef KEXI_AUTOFIELD_FORM_WIDGET_SUPPORT
449     m_fieldListView->setEnabled(hasDataSource);
450     m_availableFieldsLabel->setEnabled(hasDataSource);
451     m_mousePointerLabel->setEnabled(hasDataSource);
452     m_availableFieldsDescriptionLabel->setEnabled(hasDataSource);
453 #endif
454 }
455 
selectedPluginId() const456 QString KexiDataSourcePage::selectedPluginId() const
457 {
458     return m_formDataSourceCombo->selectedPluginId();
459 }
460 
selectedName() const461 QString KexiDataSourcePage::selectedName() const
462 {
463     return m_formDataSourceCombo->selectedName();
464 }
465