1 /****************************************************************************
2 **
3 ** Copyright (C) 2015 The Qt Company Ltd.
4 ** Contact: http://www.qt.io/licensing/
5 **
6 ** This file is part of the Qt Designer of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see http://www.qt.io/terms-conditions. For further
15 ** information use the contact form at http://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 or version 3 as published by the Free
20 ** Software Foundation and appearing in the file LICENSE.LGPLv21 and
21 ** LICENSE.LGPLv3 included in the packaging of this file. Please review the
22 ** following information to ensure the GNU Lesser General Public License
23 ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
24 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 **
26 ** As a special exception, The Qt Company gives you certain additional
27 ** rights. These rights are described in The Qt Company LGPL Exception
28 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 **
30 ** GNU General Public License Usage
31 ** Alternatively, this file may be used under the terms of the GNU
32 ** General Public License version 3.0 as published by the Free Software
33 ** Foundation and appearing in the file LICENSE.GPL included in the
34 ** packaging of this file.  Please review the following information to
35 ** ensure the GNU General Public License version 3.0 requirements will be
36 ** met: http://www.gnu.org/copyleft/gpl.html.
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41 
42 #include "qdesigner_promotiondialog_p.h"
43 #include "promotionmodel_p.h"
44 #include "iconloader_p.h"
45 #include "widgetdatabase_p.h"
46 #include "signalslotdialog_p.h"
47 
48 #include <QtDesigner/QDesignerFormEditorInterface>
49 #include <QtDesigner/QDesignerFormWindowInterface>
50 #include <QtDesigner/QDesignerPromotionInterface>
51 #include <QtDesigner/QDesignerWidgetDataBaseItemInterface>
52 #include <QtDesigner/QDesignerIntegrationInterface>
53 #include <abstractdialoggui_p.h>
54 
55 #include <QtCore/QTimer>
56 #include <QtGui/QVBoxLayout>
57 #include <QtGui/QHBoxLayout>
58 #include <QtGui/QFormLayout>
59 #include <QtGui/QDialogButtonBox>
60 #include <QtGui/QTreeView>
61 #include <QtGui/QHeaderView>
62 #include <QtGui/QPushButton>
63 #include <QtGui/QItemSelectionModel>
64 #include <QtGui/QItemSelection>
65 #include <QtGui/QComboBox>
66 #include <QtGui/QLineEdit>
67 #include <QtGui/QCheckBox>
68 #include <QtGui/QRegExpValidator>
69 #include <QtGui/QLabel>
70 #include <QtGui/QSpacerItem>
71 #include <QtGui/QMenu>
72 #include <QtGui/QAction>
73 
74 QT_BEGIN_NAMESPACE
75 
76 namespace qdesigner_internal {
77     // PromotionParameters
78     struct PromotionParameters {
79         QString m_baseClass;
80         QString m_className;
81         QString m_includeFile;
82     };
83 
84     //  NewPromotedClassPanel
NewPromotedClassPanel(const QStringList & baseClasses,int selectedBaseClass,QWidget * parent)85     NewPromotedClassPanel::NewPromotedClassPanel(const QStringList &baseClasses,
86                                                    int selectedBaseClass,
87                                                    QWidget *parent) :
88         QGroupBox(parent),
89         m_baseClassCombo(new  QComboBox),
90         m_classNameEdit(new QLineEdit),
91         m_includeFileEdit(new QLineEdit),
92         m_globalIncludeCheckBox(new QCheckBox),
93         m_addButton(new QPushButton(tr("Add")))
94     {
95         setTitle(tr("New Promoted Class"));
96         setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Maximum);
97         QHBoxLayout *hboxLayout = new QHBoxLayout(this);
98 
99         m_classNameEdit->setValidator(new QRegExpValidator(QRegExp(QLatin1String("[_a-zA-Z:][:_a-zA-Z0-9]*")), m_classNameEdit));
100         connect(m_classNameEdit,   SIGNAL(textChanged(QString)), this, SLOT(slotNameChanged(QString)));
101         connect(m_includeFileEdit, SIGNAL(textChanged(QString)), this, SLOT(slotIncludeFileChanged(QString)));
102 
103         m_baseClassCombo->setEditable(false);
104         m_baseClassCombo->addItems(baseClasses);
105         if (selectedBaseClass != -1)
106             m_baseClassCombo->setCurrentIndex(selectedBaseClass);
107 
108         // Grid
109         QFormLayout *formLayout = new QFormLayout();
110         formLayout->addRow(tr("Base class name:"),     m_baseClassCombo);
111         formLayout->addRow(tr("Promoted class name:"), m_classNameEdit);
112         formLayout->addRow(tr("Header file:"),         m_includeFileEdit);
113         formLayout->addRow(tr("Global include"),       m_globalIncludeCheckBox);
114         hboxLayout->addLayout(formLayout);
115         hboxLayout->addItem(new QSpacerItem(15, 0, QSizePolicy::Fixed, QSizePolicy::Ignored));
116         // Button box
117         QVBoxLayout *buttonLayout = new QVBoxLayout();
118 
119         m_addButton->setAutoDefault(false);
120         connect(m_addButton, SIGNAL(clicked()), this, SLOT(slotAdd()));
121         m_addButton->setEnabled(false);
122         buttonLayout->addWidget(m_addButton);
123 
124         QPushButton *resetButton = new QPushButton(tr("Reset"));
125         resetButton->setAutoDefault(false);
126         connect(resetButton, SIGNAL(clicked()), this, SLOT(slotReset()));
127 
128         buttonLayout->addWidget(resetButton);
129         buttonLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Ignored, QSizePolicy::Expanding));
130         hboxLayout->addLayout(buttonLayout);
131 
132         enableButtons();
133     }
134 
slotAdd()135     void NewPromotedClassPanel::slotAdd() {
136         bool ok = false;
137         emit newPromotedClass(promotionParameters(), &ok);
138         if (ok)
139             slotReset();
140     }
141 
slotReset()142     void NewPromotedClassPanel::slotReset() {
143         const QString empty;
144         m_classNameEdit->setText(empty);
145         m_includeFileEdit->setText(empty);
146         m_globalIncludeCheckBox->setCheckState(Qt::Unchecked);
147     }
148 
grabFocus()149     void NewPromotedClassPanel::grabFocus() {
150         m_classNameEdit->setFocus(Qt::OtherFocusReason);
151     }
152 
slotNameChanged(const QString & className)153     void NewPromotedClassPanel::slotNameChanged(const QString &className) {
154         // Suggest a name
155         if (!className.isEmpty()) {
156             const QChar dot(QLatin1Char('.'));
157             QString suggestedHeader = m_promotedHeaderLowerCase ?
158                                       className.toLower() : className;
159             suggestedHeader.replace(QLatin1String("::"), QString(QLatin1Char('_')));
160             if (!m_promotedHeaderSuffix.startsWith(dot))
161                 suggestedHeader += dot;
162             suggestedHeader += m_promotedHeaderSuffix;
163 
164             const bool blocked = m_includeFileEdit->blockSignals(true);
165             m_includeFileEdit->setText(suggestedHeader);
166             m_includeFileEdit->blockSignals(blocked);
167         }
168         enableButtons();
169     }
170 
slotIncludeFileChanged(const QString &)171     void NewPromotedClassPanel::slotIncludeFileChanged(const QString &){
172         enableButtons();
173     }
174 
enableButtons()175     void NewPromotedClassPanel::enableButtons() {
176         const bool enabled = !m_classNameEdit->text().isEmpty() && !m_includeFileEdit->text().isEmpty();
177         m_addButton->setEnabled(enabled);
178         m_addButton->setDefault(enabled);
179     }
180 
promotionParameters() const181     PromotionParameters NewPromotedClassPanel::promotionParameters() const {
182          PromotionParameters rc;
183          rc.m_baseClass = m_baseClassCombo->currentText();
184          rc.m_className = m_classNameEdit->text();
185          rc.m_includeFile = buildIncludeFile(m_includeFileEdit->text(),
186                                              m_globalIncludeCheckBox->checkState() == Qt::Checked ? IncludeGlobal : IncludeLocal);
187          return rc;
188      }
189 
chooseBaseClass(const QString & baseClass)190     void NewPromotedClassPanel::chooseBaseClass(const QString &baseClass) {
191         const int index = m_baseClassCombo->findText (baseClass);
192         if (index != -1)
193             m_baseClassCombo->setCurrentIndex (index);
194     }
195 
196     // --------------- QDesignerPromotionDialog
QDesignerPromotionDialog(QDesignerFormEditorInterface * core,QWidget * parent,const QString & promotableWidgetClassName,QString * promoteTo)197     QDesignerPromotionDialog::QDesignerPromotionDialog(QDesignerFormEditorInterface *core,
198                                                        QWidget *parent,
199                                                        const QString &promotableWidgetClassName,
200                                                        QString *promoteTo) :
201         QDialog(parent),
202         m_mode(promotableWidgetClassName.isEmpty() || promoteTo == 0 ? ModeEdit : ModeEditChooseClass),
203         m_promotableWidgetClassName(promotableWidgetClassName),
204         m_core(core),
205         m_promoteTo(promoteTo),
206         m_promotion(core->promotion()),
207         m_model(new PromotionModel(core)),
208         m_treeView(new QTreeView),
209         m_buttonBox(0),
210         m_removeButton(new QPushButton(createIconSet(QString::fromUtf8("minus.png")), QString()))
211     {
212         m_buttonBox = createButtonBox();
213         setModal(true);
214         setWindowTitle(tr("Promoted Widgets"));
215         setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
216 
217         QVBoxLayout *vboxLayout = new QVBoxLayout(this);
218 
219         // tree view group
220         QGroupBox *treeViewGroup = new QGroupBox();
221         treeViewGroup->setTitle(tr("Promoted Classes"));
222         QVBoxLayout *treeViewVBoxLayout = new QVBoxLayout(treeViewGroup);
223         // tree view
224         m_treeView->setModel (m_model);
225         m_treeView->setMinimumWidth(450);
226         m_treeView->setContextMenuPolicy(Qt::CustomContextMenu);
227 
228         connect(m_treeView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
229                 this, SLOT(slotSelectionChanged(QItemSelection,QItemSelection)));
230 
231         connect(m_treeView, SIGNAL(customContextMenuRequested(QPoint)),
232                 this, SLOT(slotTreeViewContextMenu(QPoint)));
233 
234         QHeaderView *headerView = m_treeView->header();
235         headerView->setResizeMode(QHeaderView::ResizeToContents);
236         treeViewVBoxLayout->addWidget(m_treeView);
237         // remove button
238         QHBoxLayout *hboxLayout = new QHBoxLayout();
239         hboxLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Ignored));
240 
241         m_removeButton->setAutoDefault(false);
242         connect(m_removeButton, SIGNAL(clicked()), this, SLOT(slotRemove()));
243         m_removeButton->setEnabled(false);
244         hboxLayout->addWidget(m_removeButton);
245         treeViewVBoxLayout->addLayout(hboxLayout);
246         vboxLayout->addWidget(treeViewGroup);
247         // Create new panel: Try to be smart and preselect a base class. Default to QFrame
248         const QStringList &baseClassNameList = baseClassNames(m_promotion);
249         int preselectedBaseClass = -1;
250         if (m_mode == ModeEditChooseClass) {
251             preselectedBaseClass = baseClassNameList.indexOf(m_promotableWidgetClassName);
252         }
253         if (preselectedBaseClass == -1)
254             preselectedBaseClass = baseClassNameList.indexOf(QLatin1String("QFrame"));
255 
256         NewPromotedClassPanel *newPromotedClassPanel = new NewPromotedClassPanel(baseClassNameList, preselectedBaseClass);
257         newPromotedClassPanel->setPromotedHeaderSuffix(core->integration()->headerSuffix());
258         newPromotedClassPanel->setPromotedHeaderLowerCase(core->integration()->isHeaderLowercase());
259         connect(newPromotedClassPanel, SIGNAL(newPromotedClass(PromotionParameters,bool*)), this, SLOT(slotNewPromotedClass(PromotionParameters,bool*)));
260         connect(this, SIGNAL(selectedBaseClassChanged(QString)),
261                 newPromotedClassPanel, SLOT(chooseBaseClass(QString)));
262         vboxLayout->addWidget(newPromotedClassPanel);
263         // button box
264         vboxLayout->addWidget(m_buttonBox);
265         // connect model
266         connect(m_model, SIGNAL(includeFileChanged(QDesignerWidgetDataBaseItemInterface*,QString)),
267                 this, SLOT(slotIncludeFileChanged(QDesignerWidgetDataBaseItemInterface*,QString)));
268 
269         connect(m_model, SIGNAL(classNameChanged(QDesignerWidgetDataBaseItemInterface*,QString)),
270                 this, SLOT(slotClassNameChanged(QDesignerWidgetDataBaseItemInterface*,QString)));
271 
272         // focus
273         if (m_mode == ModeEditChooseClass)
274             newPromotedClassPanel->grabFocus();
275 
276         slotUpdateFromWidgetDatabase();
277     }
278 
createButtonBox()279     QDialogButtonBox *QDesignerPromotionDialog::createButtonBox() {
280         QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Close);
281 
282         connect(buttonBox , SIGNAL(accepted()), this, SLOT(slotAcceptPromoteTo()));
283         buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Promote"));
284         buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
285 
286         connect(buttonBox , SIGNAL(rejected()), this, SLOT(reject()));
287         return buttonBox;
288     }
289 
slotUpdateFromWidgetDatabase()290     void QDesignerPromotionDialog::slotUpdateFromWidgetDatabase() {
291         m_model->updateFromWidgetDatabase();
292         m_treeView->expandAll();
293         m_removeButton->setEnabled(false);
294     }
295 
delayedUpdateFromWidgetDatabase()296     void QDesignerPromotionDialog::delayedUpdateFromWidgetDatabase() {
297         QTimer::singleShot(0, this, SLOT(slotUpdateFromWidgetDatabase()));
298     }
299 
baseClassNames(const QDesignerPromotionInterface * promotion)300     const QStringList &QDesignerPromotionDialog::baseClassNames(const QDesignerPromotionInterface *promotion) {
301         typedef QList<QDesignerWidgetDataBaseItemInterface *> WidgetDataBaseItemList;
302         static QStringList rc;
303         if (rc.empty()) {
304             // Convert the item list into a string list.
305             const WidgetDataBaseItemList dbItems =  promotion->promotionBaseClasses();
306             const WidgetDataBaseItemList::const_iterator cend =  dbItems.constEnd();
307             for (WidgetDataBaseItemList::const_iterator it = dbItems.constBegin() ; it != cend; ++it) {
308                 rc.push_back( (*it)->name());
309             }
310         }
311         return rc;
312     }
313 
slotAcceptPromoteTo()314     void  QDesignerPromotionDialog::slotAcceptPromoteTo() {
315         Q_ASSERT(m_mode == ModeEditChooseClass);
316         unsigned flags;
317         // Ok pressed: Promote to selected class
318         if (QDesignerWidgetDataBaseItemInterface *dbItem = databaseItemAt(m_treeView->selectionModel()->selection(), flags)) {
319             if (flags & CanPromote) {
320                 *m_promoteTo = dbItem ->name();
321                 accept();
322             }
323         }
324     }
325 
slotRemove()326     void QDesignerPromotionDialog::slotRemove() {
327         unsigned flags;
328         QDesignerWidgetDataBaseItemInterface *dbItem = databaseItemAt(m_treeView->selectionModel()->selection(), flags);
329         if (!dbItem || (flags & Referenced))
330             return;
331 
332         QString errorMessage;
333         if (m_promotion->removePromotedClass(dbItem->name(), &errorMessage)) {
334             slotUpdateFromWidgetDatabase();
335         } else {
336             displayError(errorMessage);
337         }
338     }
339 
slotSelectionChanged(const QItemSelection & selected,const QItemSelection &)340     void QDesignerPromotionDialog::slotSelectionChanged(const QItemSelection &selected, const QItemSelection &) {
341         // Enable deleting non-referenced items
342         unsigned flags;
343         const QDesignerWidgetDataBaseItemInterface *dbItem = databaseItemAt(selected, flags);
344         m_removeButton->setEnabled(dbItem && !(flags & Referenced));
345         // In choose mode, can we promote to the class?
346         if (m_mode == ModeEditChooseClass) {
347             const bool enablePromoted = flags & CanPromote;
348             m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(enablePromoted);
349             m_buttonBox->button(QDialogButtonBox::Ok)->setDefault(enablePromoted);
350         }
351         // different base?
352         if (dbItem) {
353             const QString baseClass = dbItem->extends();
354             if (baseClass !=  m_lastSelectedBaseClass) {
355                 m_lastSelectedBaseClass = baseClass;
356                 emit selectedBaseClassChanged(m_lastSelectedBaseClass);
357             }
358         }
359     }
360 
databaseItemAt(const QItemSelection & selected,unsigned & flags) const361     QDesignerWidgetDataBaseItemInterface *QDesignerPromotionDialog::databaseItemAt(const QItemSelection &selected, unsigned &flags) const {
362         flags = 0;
363         const QModelIndexList indexes = selected.indexes();
364         if (indexes.empty())
365             return 0;
366 
367         bool referenced;
368         QDesignerWidgetDataBaseItemInterface *dbItem = m_model->databaseItemAt(indexes.front(), &referenced);
369 
370         if (dbItem) {
371             if (referenced)
372                 flags |= Referenced;
373             // In choose mode, can we promote to the class?
374             if (m_mode == ModeEditChooseClass &&  dbItem && dbItem->isPromoted() && dbItem->extends() ==  m_promotableWidgetClassName)
375                 flags |= CanPromote;
376 
377         }
378         return dbItem;
379     }
380 
slotNewPromotedClass(const PromotionParameters & p,bool * ok)381     void QDesignerPromotionDialog::slotNewPromotedClass(const PromotionParameters &p, bool *ok) {
382         QString  errorMessage;
383         *ok = m_promotion->addPromotedClass(p.m_baseClass, p.m_className, p.m_includeFile, &errorMessage);
384         if (*ok) {
385             // update and select
386             slotUpdateFromWidgetDatabase();
387             const QModelIndex newClassIndex = m_model->indexOfClass(p.m_className);
388             if (newClassIndex.isValid()) {
389                 m_treeView->selectionModel()->select(newClassIndex, QItemSelectionModel::SelectCurrent|QItemSelectionModel::Rows);
390             }
391         } else {
392             displayError(errorMessage);
393         }
394     }
395 
slotIncludeFileChanged(QDesignerWidgetDataBaseItemInterface * dbItem,const QString & includeFile)396     void QDesignerPromotionDialog::slotIncludeFileChanged(QDesignerWidgetDataBaseItemInterface *dbItem, const QString &includeFile) {
397         if (includeFile.isEmpty()) {
398             delayedUpdateFromWidgetDatabase();
399             return;
400         }
401 
402         if (dbItem->includeFile() == includeFile)
403             return;
404 
405         QString errorMessage;
406         if (!m_promotion->setPromotedClassIncludeFile(dbItem->name(), includeFile, &errorMessage)) {
407             displayError(errorMessage);
408             delayedUpdateFromWidgetDatabase();
409         }
410     }
411 
slotClassNameChanged(QDesignerWidgetDataBaseItemInterface * dbItem,const QString & newName)412     void  QDesignerPromotionDialog::slotClassNameChanged(QDesignerWidgetDataBaseItemInterface *dbItem, const QString &newName) {
413         if (newName.isEmpty()) {
414             delayedUpdateFromWidgetDatabase();
415             return;
416         }
417         const QString oldName = dbItem->name();
418         if (newName == oldName)
419             return;
420 
421         QString errorMessage;
422         if (!m_promotion->changePromotedClassName(oldName , newName, &errorMessage)) {
423             displayError(errorMessage);
424             delayedUpdateFromWidgetDatabase();
425         }
426     }
427 
slotTreeViewContextMenu(const QPoint & pos)428     void QDesignerPromotionDialog::slotTreeViewContextMenu(const QPoint &pos) {
429         unsigned flags;
430         const QDesignerWidgetDataBaseItemInterface *dbItem = databaseItemAt(m_treeView->selectionModel()->selection(), flags);
431         if (!dbItem)
432             return;
433 
434         QMenu menu;
435         QAction *signalSlotAction = menu.addAction(tr("Change signals/slots..."));
436         connect(signalSlotAction, SIGNAL(triggered()), this, SLOT(slotEditSignalsSlots()));
437 
438         menu.exec(m_treeView->viewport()->mapToGlobal(pos));
439     }
440 
slotEditSignalsSlots()441     void  QDesignerPromotionDialog::slotEditSignalsSlots() {
442         unsigned flags;
443         const QDesignerWidgetDataBaseItemInterface *dbItem = databaseItemAt(m_treeView->selectionModel()->selection(), flags);
444         if (!dbItem)
445             return;
446 
447         SignalSlotDialog::editPromotedClass(m_core, dbItem->name(), this);
448     }
449 
displayError(const QString & message)450     void QDesignerPromotionDialog::displayError(const QString &message) {
451         m_core->dialogGui()->message(this, QDesignerDialogGuiInterface::PromotionErrorMessage, QMessageBox::Warning,
452                                      tr("%1 - Error").arg(windowTitle()), message,  QMessageBox::Close);
453     }
454 } // namespace qdesigner_internal
455 
456 QT_END_NAMESPACE
457