1 /* This file is part of the KDE project
2    Copyright (C) 2005-2011 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 "kexiformmanager.h"
21 #include "kexidbform.h"
22 #ifdef KEXI_AUTOFIELD_FORM_WIDGET_SUPPORT
23 #include "kexidbautofield.h"
24 #endif
25 #include "kexiformscrollview.h"
26 #include "kexiformview.h"
27 #include "kexidatasourcepage.h"
28 
29 #include <KexiIcon.h>
30 
31 #include <QDomDocument>
32 #include <QAction>
33 #include <QDebug>
34 
35 #include <KToggleAction>
36 #include <KActionCollection>
37 #include <KPageDialog>
38 #include <KTextEdit>
39 #include <KToolBar>
40 #include <KSharedConfig>
41 #include <KConfigGroup>
42 
43 #include <formeditor/form.h>
44 #include <formeditor/widgetlibrary.h>
45 #include <formeditor/commands.h>
46 #include <formeditor/objecttree.h>
47 #include <formeditor/formIO.h>
48 #include <formeditor/kexiactionselectiondialog.h>
49 #include <formeditor/WidgetTreeWidget.h>
50 
51 #include <KPropertySet>
52 #include <KProperty>
53 #include <widget/properties/KexiCustomPropertyFactory.h>
54 #include <core/KexiMainWindowIface.h>
55 #include <kexiutils/SmallToolButton.h>
56 #include <kexiutils/utils.h>
57 #include <config-kexi.h>
58 
59 class KexiFormManagerPrivate {
60 public:
KexiFormManagerPrivate(KexiFormManager * qq)61     explicit KexiFormManagerPrivate(KexiFormManager *qq) : part(0)
62         , q(qq)
63     {
64         features = KFormDesigner::Form::NoFeatures;
65         widgetActionGroup = new KFormDesigner::ActionGroup(q);
66 #ifdef KFD_SIGSLOTS
67         dragConnectionAction = 0;
68 #endif
69         widgetTree = 0;
70         collection = 0;
71     }
~KexiFormManagerPrivate()72     ~KexiFormManagerPrivate() {
73     }
74     KexiFormPart* part;
75     KFormDesigner::WidgetLibrary* lib;
76     KFormDesigner::ActionGroup* widgetActionGroup;
77     KFormDesigner::WidgetTreeWidget *widgetTree;
78     KActionCollection  *collection;
79     KFormDesigner::Form::Features features;
80     KToggleAction *pointerAction;
81 #ifdef KFD_SIGSLOTS
82     KToggleAction *dragConnectionAction;
83 #endif
84     KToggleAction *snapToGridAction;
85 
86     KexiFormManager *q;
87 };
88 
Q_GLOBAL_STATIC(KexiFormManager,g_manager)89 Q_GLOBAL_STATIC(KexiFormManager, g_manager)
90 
91 KexiFormManager* KexiFormManager::self()
92 {
93     return g_manager;
94 }
95 
KexiFormManager()96 KexiFormManager::KexiFormManager()
97         : QObject()
98         , d(new KexiFormManagerPrivate(this))
99 {
100     // needed for custom "pixmap" property editor widget
101     KexiCustomPropertyFactory::init();
102 }
103 
~KexiFormManager()104 KexiFormManager::~KexiFormManager()
105 {
106     delete d;
107 }
108 
init(KexiFormPart * part,KFormDesigner::WidgetTreeWidget * widgetTree)109 void KexiFormManager::init(KexiFormPart *part, KFormDesigner::WidgetTreeWidget *widgetTree)
110 {
111 /*! @todo add configuration for supported factory groups */
112     QStringList supportedFactoryGroups;
113     supportedFactoryGroups += "kexi";
114     d->lib = new KFormDesigner::WidgetLibrary(this, supportedFactoryGroups);
115     d->lib->setAdvancedPropertiesVisible(false);
116 
117     connect(d->lib, SIGNAL(widgetCreated(QWidget*)),
118             this, SLOT(slotWidgetCreatedByFormsLibrary(QWidget*)));
119     connect(d->lib, SIGNAL(widgetActionToggled(QByteArray)),
120         this, SLOT(slotWidgetActionToggled(QByteArray)));
121 
122     d->part = part;
123     KActionCollection *col = /*tmp*/ new KActionCollection(this);
124     if (col) {
125         createActions( col );
126         //connect actions provided by widget factories
127         connect(col->action("widget_assign_action"), SIGNAL(triggered()),
128                 this, SLOT(slotAssignAction()));
129     }
130 
131     d->widgetTree = widgetTree;
132     if (d->widgetTree) {
133 //! @todo KEXI3 Port this: connect()
134 //! @todo KEXI3 Port code related to KFormDesigner::FormManager::m_treeview here
135 //! @todo        connect(m_propSet, SIGNAL(widgetNameChanged(QByteArray,QByteArray)),
136 //! @todo                m_treeview, SLOT(renameItem(QByteArray,QByteArray)));
137     }
138 }
139 
widgetActionGroup() const140 KFormDesigner::ActionGroup* KexiFormManager::widgetActionGroup() const
141 {
142     return d->widgetActionGroup;
143 }
144 
createActions(KActionCollection * collection)145 void KexiFormManager::createActions(KActionCollection* collection)
146 {
147     d->collection = collection;
148     d->lib->createWidgetActions(d->widgetActionGroup);
149 //! @todo insertWidget() slot?
150 
151 #ifdef KFD_SIGSLOTS
152     if (d->features & KFormDesigner::Form::EnableConnections) {
153         // nothing
154     }
155     else {
156         d->dragConnectionAction = new KToggleAction(
157             KexiIcon("signalslot"), futureI18n("Connect Signals/Slots"), d->collection);
158         d->dragConnectionAction->setObjectName("drag_connection");
159         connect(d->dragConnectionAction, SIGNAL(triggered()),
160                 this, SLOT(startCreatingConnection()));
161         d->dragConnectionAction->setChecked(false);
162     }
163 #endif
164 
165     d->pointerAction = new KToggleAction(
166         koIcon("tool-pointer"), xi18n("Pointer"), d->collection);
167     d->pointerAction->setObjectName("edit_pointer");
168     d->widgetActionGroup->addAction(d->pointerAction);
169     connect(d->pointerAction, SIGNAL(triggered()),
170             this, SLOT(slotPointerClicked()));
171     d->pointerAction->setChecked(true);
172 
173     d->snapToGridAction = new KToggleAction(
174         xi18n("Snap to Grid"), d->collection);
175     d->snapToGridAction->setObjectName("snap_to_grid");
176 
177 //! @todo
178 #if 0
179     // Create the Style selection action (with a combo box in toolbar and submenu items)
180     KSelectAction *styleAction = new KSelectAction(
181         xi18n("Style"), d->collection);
182     styleAction->setObjectName("change_style");
183     connect(styleAction, SIGNAL(triggered()),
184             this, SLOT(slotStyle()));
185     styleAction->setEditable(false);
186 
187     QString currentStyle(kapp->style()->objectName().toLower());
188     const QStringList styles = QStyleFactory::keys();
189     styleAction->setItems(styles);
190     styleAction->setCurrentItem(0);
191 
192     QStringList::ConstIterator endIt = styles.constEnd();
193     int idx = 0;
194     for (QStringList::ConstIterator it = styles.constBegin(); it != endIt; ++it, ++idx) {
195         if ((*it).toLower() == currentStyle) {
196             styleAction->setCurrentItem(idx);
197             break;
198         }
199     }
200     styleAction->setToolTip(xi18n("Set the current view style."));
201     styleAction->setMenuAccelsEnabled(true);
202 #endif
203 
204     d->lib->addCustomWidgetActions(d->collection);
205 
206 #ifdef KEXI_DEBUG_GUI
207     KConfigGroup generalGroup(KSharedConfig::openConfig()->group("General"));
208     if (generalGroup.readEntry("ShowInternalDebugger", false)) {
209         QAction *a = new QAction(koIcon("run-build-file"), xi18n("Show Form UI Code"), this);
210         d->collection->addAction("show_form_ui", a);
211         a->setShortcut(Qt::CTRL + Qt::Key_U);
212         connect(a, SIGNAL(triggered()), this, SLOT(showFormUICode()));
213     }
214 #endif
215 
216 //! @todo move elsewhere
217     {
218         // (from obsolete kexiformpartinstui.rc)
219         QStringList formActions;
220         formActions
221             << "edit_pointer"
222             << QString() //sep
223 #ifndef KEXI_AUTOFIELD_FORM_WIDGET_SUPPORT
224             << "library_widget_KexiDBAutoField"
225 #endif
226             << "library_widget_KexiDBLabel"
227             << "library_widget_KexiDBLineEdit"
228             << "library_widget_KexiDBTextEdit"
229             << "library_widget_KexiDBComboBox"
230             << "library_widget_KexiDBCheckBox"
231             << "library_widget_KexiDBImageBox"
232             << QString() //sep
233             << "library_widget_KexiDBPushButton"
234             << QString() //sep
235             << "library_widget_KexiFrame"
236             << "library_widget_QGroupBox"
237             << "library_widget_KFDTabWidget"
238             << "library_widget_KexiLineWidget"
239             << QString() //sep
240 #ifdef HAVE_QTWEBKITWIDGETS
241             << "library_widget_WebBrowserWidget"
242 #endif
243 #ifdef HAVE_MARBLE
244             << "library_widget_MapBrowserWidget"
245 #endif
246             << "library_widget_KexiDBSlider"
247             << "library_widget_KexiDBProgressBar"
248             << "library_widget_KexiDBCommandLinkButton"
249             << "library_widget_KexiDBDatePicker"
250             << QString() //sep
251             ;
252         KexiMainWindowIface *win = KexiMainWindowIface::global();
253         foreach( const QString& actionName_, formActions ) {
254             QAction *a;
255             const QString actionName(actionName_.startsWith(':') ? actionName_.mid(1) : actionName_);
256             if (actionName.isEmpty()) {
257                 a = new QAction(this);
258                 a->setSeparator(true);
259             }
260             else {
261                 a = d->widgetActionGroup->action(actionName);
262             }
263             if (actionName_.startsWith(':')) {  // icon only
264                 KexiSmallToolButton *btn = new KexiSmallToolButton(a, win->toolBar("form"));
265                 btn->setToolButtonStyle(Qt::ToolButtonIconOnly);
266                 win->appendWidgetToToolbar("form", btn);
267             }
268             else {
269                 win->addToolBarAction("form", a);
270             }
271         }
272 
273         QSet<QString> iconOnlyActions;
274         const QList<QAction*> actions( d->collection->actions() );
275         foreach( QAction *a, actions ) {
276             if (iconOnlyActions.contains(a->objectName())) { // icon only
277                 KexiSmallToolButton *btn = new KexiSmallToolButton(a, win->toolBar("form"));
278                 btn->setToolButtonStyle(Qt::ToolButtonIconOnly);
279                 win->appendWidgetToToolbar("form", btn);
280                 win->setWidgetVisibleInToolbar(btn, true);
281             }
282             else {
283                 win->addToolBarAction("form", a);
284             }
285         }
286     }
287 }
288 
slotWidgetCreatedByFormsLibrary(QWidget * widget)289 void KexiFormManager::slotWidgetCreatedByFormsLibrary(QWidget* widget)
290 {
291     QList<QMetaMethod> _signals(KexiUtils::methodsForMetaObject(
292                                     widget->metaObject(), QMetaMethod::Signal));
293 
294     if (!_signals.isEmpty()) {
295         QByteArray handleDragMoveEventSignal = "handleDragMoveEvent(QDragMoveEvent*)";
296         QByteArray handleDropEventSignal = "handleDropEvent(QDropEvent*)";
297         KexiFormView *formView = KDbUtils::findParent<KexiFormView*>(widget);
298 
299         foreach(const QMetaMethod& method, _signals) {
300             if (0 == qstrcmp(method.methodSignature(), handleDragMoveEventSignal)) {
301                 //qDebug() << method.methodSignature();
302                 if (formView) {
303                     connect(widget, SIGNAL(handleDragMoveEvent(QDragMoveEvent*)),
304                             formView, SLOT(slotHandleDragMoveEvent(QDragMoveEvent*)));
305                 }
306             } else if (0 == qstrcmp(method.methodSignature(), handleDropEventSignal)) {
307                 //qDebug() << method.methodSignature();
308                 if (formView) {
309                     connect(widget, SIGNAL(handleDropEvent(QDropEvent*)),
310                             formView, SLOT(slotHandleDropEvent(QDropEvent*)));
311                 }
312             }
313         }
314     }
315 }
316 
slotWidgetActionToggled(const QByteArray & action)317 void KexiFormManager::slotWidgetActionToggled(const QByteArray& action)
318 {
319     KexiFormView* fv = activeFormViewWidget();
320     if (fv) {
321         fv->form()->enterWidgetInsertingState(action);
322     }
323 }
324 
library() const325 KFormDesigner::WidgetLibrary* KexiFormManager::library() const
326 {
327     return d->lib;
328 }
329 
action(const char * name)330 QAction* KexiFormManager::action(const char* name)
331 {
332     KActionCollection *col = d->part->actionCollectionForMode(Kexi::DesignViewMode);
333     if (!col)
334         return 0;
335     QString n(translateName(name));
336     QAction *a = col->action(n);
337     if (a)
338         return a;
339     if (activeFormViewWidget()) {
340         a = KexiMainWindowIface::global()->actionCollection()->action(n);
341         if (a)
342             return a;
343     }
344     return d->collection->action(name);
345 }
346 
activeFormViewWidget() const347 KexiFormView* KexiFormManager::activeFormViewWidget() const
348 {
349     KexiWindow *currentWindow = KexiMainWindowIface::global()->currentWindow();
350     if (!currentWindow)
351         return 0;
352     KexiFormView *currentView = dynamic_cast<KexiFormView*>(currentWindow->selectedView());
353     if (!currentView || currentView->viewMode()!=Kexi::DesignViewMode) {
354         return 0;
355     }
356     KFormDesigner::Form *form = currentView->form();
357     if (!form) {
358         return 0;
359     }
360     KexiDBForm *dbform = dynamic_cast<KexiDBForm*>(form->formWidget());
361     if (!dbform) {
362         return 0;
363     }
364     KexiFormScrollView *scrollViewWidget = dynamic_cast<KexiFormScrollView*>(dbform->dataAwareObject());
365     if (!scrollViewWidget)
366         return 0;
367     return dynamic_cast<KexiFormView*>(scrollViewWidget->parent());
368 }
369 
enableAction(const char * name,bool enable)370 void KexiFormManager::enableAction(const char* name, bool enable)
371 {
372     KexiFormView* formViewWidget = activeFormViewWidget();
373     if (!formViewWidget)
374         return;
375     formViewWidget->setAvailable(translateName(name).toLatin1(), enable);
376 }
377 
setFormDataSource(const QString & pluginId,const QString & name)378 void KexiFormManager::setFormDataSource(const QString& pluginId, const QString& name)
379 {
380     KexiFormView* formViewWidget = activeFormViewWidget();
381     if (!formViewWidget)
382         return;
383     KexiDBForm* formWidget = dynamic_cast<KexiDBForm*>(formViewWidget->form()->widget());
384     if (!formWidget)
385         return;
386 
387     QString oldDataSourcePartClass(formWidget->dataSourcePluginId());
388     QString oldDataSource(formWidget->dataSource());
389     if (pluginId != oldDataSourcePartClass || name != oldDataSource) {
390         QHash<QByteArray, QVariant> propValues;
391         propValues.insert("dataSource", name);
392         propValues.insert("dataSourcePartClass", pluginId);
393         KFormDesigner::PropertyCommandGroup *group = new KFormDesigner::PropertyCommandGroup(
394             xi18n("Set form's data source to <resource>%1</resource>", name));
395         formViewWidget->form()->createPropertyCommandsInDesignMode(
396             formWidget, propValues, group, true /*addToActiveForm*/);
397     }
398 }
399 
setDataSourceFieldOrExpression(const QString & string,const QString & caption,KDbField::Type type)400 void KexiFormManager::setDataSourceFieldOrExpression(
401     const QString& string, const QString& caption, KDbField::Type type)
402 {
403     KexiFormView* formViewWidget = activeFormViewWidget();
404     if (!formViewWidget)
405         return;
406 
407     KPropertySet* set = formViewWidget->form()->propertySet();
408     if (!set->contains("dataSource"))
409         return;
410 
411     set->property("dataSource").setValue(string);
412 
413     if (set->propertyValue("autoCaption", false).toBool()) {
414         set->changePropertyIfExists("fieldCaptionInternal", caption);
415     }
416     if (set->propertyValue("widgetType").toString() == "Auto") {
417         set->changePropertyIfExists("fieldTypeInternal", type);
418     }
419 }
420 
insertAutoFields(const QString & sourcePartClass,const QString & sourceName,const QStringList & fields)421 void KexiFormManager::insertAutoFields(const QString& sourcePartClass, const QString& sourceName,
422                                        const QStringList& fields)
423 {
424 #ifdef KEXI_AUTOFIELD_FORM_WIDGET_SUPPORT
425     Q_UNUSED(sourcePartClass);
426     Q_UNUSED(sourceName);
427     Q_UNUSED(fields);
428 #else
429     KexiFormView* formViewWidget = activeFormViewWidget();
430     if (!formViewWidget || !formViewWidget->form() || !formViewWidget->form()->activeContainer())
431         return;
432     formViewWidget->insertAutoFields(sourcePartClass, sourceName, fields,
433                                      formViewWidget->form()->activeContainer());
434 #endif
435 }
436 
slotHistoryCommandExecuted(KFormDesigner::Command * command)437 void KexiFormManager::slotHistoryCommandExecuted(KFormDesigner::Command *command)
438 {
439     if (command->childCount() == 2) {
440         KexiFormView* formViewWidget = activeFormViewWidget();
441         if (!formViewWidget)
442             return;
443         KexiDBForm* formWidget = dynamic_cast<KexiDBForm*>(formViewWidget->form()->widget());
444         if (!formWidget)
445             return;
446         const KFormDesigner::PropertyCommand* pc1
447             = dynamic_cast<const KFormDesigner::PropertyCommand*>(command->child(0));
448         const KFormDesigner::PropertyCommand* pc2
449             = dynamic_cast<const KFormDesigner::PropertyCommand*>(command->child(1));
450         if (pc1 && pc2 && pc1->propertyName() == "dataSource" && pc2->propertyName() == "dataSourcePartClass") {
451             const QHash<QByteArray, QVariant>::const_iterator it1(pc1->oldValues().constBegin());
452             const QHash<QByteArray, QVariant>::const_iterator it2(pc2->oldValues().constBegin());
453             if (it1.key() == formWidget->objectName() && it2.key() == formWidget->objectName())
454                 d->part->dataSourcePage()->setFormDataSource(
455                     formWidget->dataSourcePluginId(), formWidget->dataSource());
456         }
457     }
458 }
459 
showFormUICode()460 void KexiFormManager::showFormUICode()
461 {
462 #ifdef KEXI_DEBUG_GUI
463     KexiFormView* formView = activeFormViewWidget();
464     if (!formView)
465         return;
466     formView->form()->resetInlineEditor();
467 
468     QString uiCode;
469     const int indent = 2;
470     if (!KFormDesigner::FormIO::saveFormToString(formView->form(), uiCode, indent)) {
471         //! @todo show err?
472         return;
473     }
474 
475     KPageDialog uiCodeDialog;
476     uiCodeDialog.setFaceType(KPageDialog::Tabbed);
477     uiCodeDialog.setModal(true);
478     uiCodeDialog.setWindowTitle(xi18nc("@title:window", "Form's UI Code"));
479     uiCodeDialog.resize(700, 600);
480     KTextEdit *currentUICodeDialogEditor = new KTextEdit(&uiCodeDialog);
481     uiCodeDialog.addPage(currentUICodeDialogEditor, xi18n("Current"));
482     currentUICodeDialogEditor->setReadOnly(true);
483     QFont f(currentUICodeDialogEditor->font());
484     f.setFamily("courier");
485     currentUICodeDialogEditor->setFont(f);
486 
487     KTextEdit *originalUICodeDialogEditor = new KTextEdit(&uiCodeDialog);
488     uiCodeDialog.addPage(originalUICodeDialogEditor, xi18n("Original"));
489     originalUICodeDialogEditor->setReadOnly(true);
490     originalUICodeDialogEditor->setFont(f);
491     currentUICodeDialogEditor->setPlainText(uiCode);
492     //indent and set our original doc as well:
493     QDomDocument doc;
494     doc.setContent(formView->form()->m_recentlyLoadedUICode);
495     originalUICodeDialogEditor->setPlainText(doc.toString(indent));
496     uiCodeDialog.exec();
497 #endif
498 }
499 
slotAssignAction()500 void KexiFormManager::slotAssignAction()
501 {
502     KexiFormView* formView = activeFormViewWidget();
503     if (!formView)
504         return;
505     KFormDesigner::Form *form = formView->form();
506     KexiDBForm *dbform = 0;
507     if (form->mode() != KFormDesigner::Form::DesignMode
508         || !(dbform = dynamic_cast<KexiDBForm*>(form->formWidget())))
509     {
510         return;
511     }
512 
513     KPropertySet* set = form->propertySet();
514 
515     KexiFormEventAction::ActionData data;
516     const KProperty &onClickActionProp = set->property("onClickAction");
517     if (!onClickActionProp.isNull())
518         data.string = onClickActionProp.value().toString();
519 
520     const KProperty &onClickActionOptionProp = set->property("onClickActionOption");
521     if (!onClickActionOptionProp.isNull())
522         data.option = onClickActionOptionProp.value().toString();
523 
524     KexiFormScrollView *scrollViewWidget
525         = dynamic_cast<KexiFormScrollView*>(dbform->dataAwareObject());
526     if (!scrollViewWidget)
527         return;
528     KexiFormView* formViewWidget = dynamic_cast<KexiFormView*>(scrollViewWidget->parent());
529     if (!formViewWidget)
530         return;
531 
532     KexiActionSelectionDialog dlg(dbform, data,
533                                   set->property("objectName").value().toString());
534 
535     if (dlg.exec() == QDialog::Accepted) {
536         data = dlg.currentAction();
537         //update property value
538         set->changeProperty("onClickAction", data.string);
539         set->changeProperty("onClickActionOption", data.option);
540     }
541 }
542 
slotPointerClicked()543 void KexiFormManager::slotPointerClicked()
544 {
545     KexiFormView* formView = activeFormViewWidget();
546     if (!formView)
547         return;
548     formView->form()->enterWidgetSelectingState();
549 }
550 
translateName(const char * name) const551 QString KexiFormManager::translateName(const char* name) const
552 {
553     QString n(QString::fromLatin1(name));
554     // translate to our name space:
555     if (n.startsWith("align_") || n.startsWith("adjust_")
556             || n == "format_raise" || n == "format_lower" || n == "taborder")
557     {
558         n.prepend("formpart_");
559     }
560     return n;
561 }
562 
563