1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the Qt Designer of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
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 https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28
29#include "qdesigner_promotion_p.h"
30#include "widgetdatabase_p.h"
31#include "metadatabase_p.h"
32#include "widgetdatabase_p.h"
33
34#include <QtDesigner/abstractformeditor.h>
35#include <QtDesigner/abstractformwindow.h>
36#include <QtDesigner/abstractformwindowmanager.h>
37#include <QtDesigner/abstractobjectinspector.h>
38#include <QtDesigner/abstractwidgetbox.h>
39#include <QtDesigner/abstractwidgetdatabase.h>
40
41#include <QtCore/qmap.h>
42#include <QtCore/qcoreapplication.h>
43#include <qdebug.h>
44
45QT_BEGIN_NAMESPACE
46
47namespace {
48 // Return a set of on-promotable classes
49 const QSet<QString> &nonPromotableClasses() {
50 static QSet<QString> rc;
51 if (rc.isEmpty()) {
52 rc.insert(QStringLiteral("Line"));
53 rc.insert(QStringLiteral("QAction"));
54 rc.insert(QStringLiteral("Spacer"));
55 rc.insert(QStringLiteral("QMainWindow"));
56 rc.insert(QStringLiteral("QDialog"));
57 rc.insert(QStringLiteral("QMdiArea"));
58 rc.insert(QStringLiteral("QMdiSubWindow"));
59 }
60 return rc;
61 }
62
63 // Return widget database index of a promoted class or -1 with error message
64 int promotedWidgetDataBaseIndex(const QDesignerWidgetDataBaseInterface *widgetDataBase,
65 const QString &className,
66 QString *errorMessage) {
67 const int index = widgetDataBase->indexOfClassName(className);
68 if (index == -1 || !widgetDataBase->item(index)->isPromoted()) {
69 *errorMessage = QCoreApplication::tr(s: "%1 is not a promoted class.").arg(a: className);
70 return -1;
71 }
72 return index;
73 }
74
75 // Return widget database item of a promoted class or 0 with error message
76 QDesignerWidgetDataBaseItemInterface *promotedWidgetDataBaseItem(const QDesignerWidgetDataBaseInterface *widgetDataBase,
77 const QString &className,
78 QString *errorMessage) {
79
80 const int index = promotedWidgetDataBaseIndex(widgetDataBase, className, errorMessage);
81 if (index == -1)
82 return nullptr;
83 return widgetDataBase->item(index);
84 }
85
86 // extract class name from xml "<widget class="QWidget" ...>". Quite a hack.
87 QString classNameFromXml(QString xml) {
88 static const QString tag = QStringLiteral("class=\"");
89 const int pos = xml.indexOf(s: tag);
90 if (pos == -1)
91 return QString();
92 xml.remove(i: 0, len: pos + tag.size());
93 const int closingPos = xml.indexOf(c: QLatin1Char('"'));
94 if (closingPos == -1)
95 return QString();
96 xml.remove(i: closingPos, len: xml.size() - closingPos);
97 return xml;
98 }
99
100 // return a list of class names in the scratch pad
101 QStringList getScratchPadClasses(const QDesignerWidgetBoxInterface *wb) {
102 QStringList rc;
103 const int catCount = wb->categoryCount();
104 for (int c = 0; c < catCount; c++) {
105 const QDesignerWidgetBoxInterface::Category category = wb->category(cat_idx: c);
106 if (category.type() == QDesignerWidgetBoxInterface::Category::Scratchpad) {
107 const int widgetCount = category.widgetCount();
108 for (int w = 0; w < widgetCount; w++) {
109 const QString className = classNameFromXml( xml: category.widget(idx: w).domXml());
110 if (!className.isEmpty())
111 rc += className;
112 }
113 }
114 }
115 return rc;
116 }
117}
118
119static void markFormsDirty(const QDesignerFormEditorInterface *core)
120{
121 const QDesignerFormWindowManagerInterface *fwm = core->formWindowManager();
122 for (int f = 0, count = fwm->formWindowCount(); f < count; ++f)
123 fwm->formWindow(index: f)->setDirty(true);
124}
125
126namespace qdesigner_internal {
127
128 QDesignerPromotion::QDesignerPromotion(QDesignerFormEditorInterface *core) :
129 m_core(core) {
130 }
131
132 bool QDesignerPromotion::addPromotedClass(const QString &baseClass,
133 const QString &className,
134 const QString &includeFile,
135 QString *errorMessage)
136 {
137 QDesignerWidgetDataBaseInterface *widgetDataBase = m_core->widgetDataBase();
138 const int baseClassIndex = widgetDataBase->indexOfClassName(className: baseClass);
139
140 if (baseClassIndex == -1) {
141 *errorMessage = QCoreApplication::tr(s: "The base class %1 is invalid.").arg(a: baseClass);
142 return false;
143 }
144
145 const int existingClassIndex = widgetDataBase->indexOfClassName(className);
146
147 if (existingClassIndex != -1) {
148 *errorMessage = QCoreApplication::tr(s: "The class %1 already exists.").arg(a: className);
149 return false;
150 }
151 // Clone derived item.
152 QDesignerWidgetDataBaseItemInterface *promotedItem = WidgetDataBaseItem::clone(item: widgetDataBase->item(index: baseClassIndex));
153 // Also inherit the container flag in case of QWidget-derived classes
154 // as it is most likely intended for stacked pages.
155 // set new props
156 promotedItem->setName(className);
157 promotedItem->setGroup(QCoreApplication::tr(s: "Promoted Widgets"));
158 promotedItem->setCustom(true);
159 promotedItem->setPromoted(true);
160 promotedItem->setExtends(baseClass);
161 promotedItem->setIncludeFile(includeFile);
162 widgetDataBase->append(item: promotedItem);
163 markFormsDirty(core: m_core);
164 return true;
165 }
166
167 QList<QDesignerWidgetDataBaseItemInterface *> QDesignerPromotion::promotionBaseClasses() const
168 {
169 using SortedDatabaseItemMap = QMap<QString, QDesignerWidgetDataBaseItemInterface *>;
170 SortedDatabaseItemMap sortedDatabaseItemMap;
171
172 QDesignerWidgetDataBaseInterface *widgetDataBase = m_core->widgetDataBase();
173
174 const int cnt = widgetDataBase->count();
175 for (int i = 0; i < cnt; i++) {
176 QDesignerWidgetDataBaseItemInterface *dbItem = widgetDataBase->item(index: i);
177 if (canBePromoted(dbItem)) {
178 sortedDatabaseItemMap.insert(akey: dbItem->name(), avalue: dbItem);
179 }
180 }
181
182 return sortedDatabaseItemMap.values();
183 }
184
185
186 bool QDesignerPromotion::canBePromoted(const QDesignerWidgetDataBaseItemInterface *dbItem) const
187 {
188 if (dbItem->isPromoted() || !dbItem->extends().isEmpty())
189 return false;
190
191 const QString name = dbItem->name();
192
193 if (nonPromotableClasses().contains(value: name))
194 return false;
195
196 if (name.startsWith(QStringLiteral("QDesigner")) ||
197 name.startsWith(QStringLiteral("QLayout")))
198 return false;
199
200 return true;
201 }
202
203 QDesignerPromotion::PromotedClasses QDesignerPromotion::promotedClasses() const
204 {
205 using ClassNameItemMap = QMap<QString, QDesignerWidgetDataBaseItemInterface *>;
206 // A map containing base classes and their promoted classes.
207 using BaseClassPromotedMap = QMap<QString, ClassNameItemMap>;
208
209 BaseClassPromotedMap baseClassPromotedMap;
210
211 QDesignerWidgetDataBaseInterface *widgetDataBase = m_core->widgetDataBase();
212 // Look for promoted classes and insert into map according to base class.
213 const int cnt = widgetDataBase->count();
214 for (int i = 0; i < cnt; i++) {
215 QDesignerWidgetDataBaseItemInterface *dbItem = widgetDataBase->item(index: i);
216 if (dbItem->isPromoted()) {
217 const QString baseClassName = dbItem->extends();
218 BaseClassPromotedMap::iterator it = baseClassPromotedMap.find(akey: baseClassName);
219 if (it == baseClassPromotedMap.end()) {
220 it = baseClassPromotedMap.insert(akey: baseClassName, avalue: ClassNameItemMap());
221 }
222 it.value().insert(akey: dbItem->name(), avalue: dbItem);
223 }
224 }
225 // convert map into list.
226 PromotedClasses rc;
227
228 if (baseClassPromotedMap.isEmpty())
229 return rc;
230
231 const BaseClassPromotedMap::const_iterator bcend = baseClassPromotedMap.constEnd();
232 for (BaseClassPromotedMap::const_iterator bit = baseClassPromotedMap.constBegin(); bit != bcend; ++bit) {
233 const int baseIndex = widgetDataBase->indexOfClassName(className: bit.key());
234 Q_ASSERT(baseIndex >= 0);
235 QDesignerWidgetDataBaseItemInterface *baseItem = widgetDataBase->item(index: baseIndex);
236 // promoted
237 const ClassNameItemMap::const_iterator pcend = bit.value().constEnd();
238 for (ClassNameItemMap::const_iterator pit = bit.value().constBegin(); pit != pcend; ++pit) {
239 PromotedClass item;
240 item.baseItem = baseItem;
241 item.promotedItem = pit.value();
242 rc.push_back(t: item);
243 }
244 }
245
246 return rc;
247 }
248
249 QSet<QString> QDesignerPromotion::referencedPromotedClassNames() const {
250 QSet<QString> rc;
251 const MetaDataBase *metaDataBase = qobject_cast<const MetaDataBase*>(object: m_core->metaDataBase());
252 if (!metaDataBase)
253 return rc;
254
255 const QObjectList &objects = metaDataBase->objects();
256 for (QObject *object : objects) {
257 const QString customClass = metaDataBase->metaDataBaseItem(object)->customClassName();
258 if (!customClass.isEmpty())
259 rc.insert(value: customClass);
260
261 }
262 // check the scratchpad of the widget box
263 if (QDesignerWidgetBoxInterface *widgetBox = m_core->widgetBox()) {
264 const QStringList scratchPadClasses = getScratchPadClasses(wb: widgetBox);
265 if (!scratchPadClasses.isEmpty()) {
266 // Check whether these are actually promoted
267 QDesignerWidgetDataBaseInterface *widgetDataBase = m_core->widgetDataBase();
268 QStringList::const_iterator cend = scratchPadClasses.constEnd();
269 for (QStringList::const_iterator it = scratchPadClasses.constBegin(); it != cend; ++it ) {
270 const int index = widgetDataBase->indexOfClassName(className: *it);
271 if (index != -1 && widgetDataBase->item(index)->isPromoted())
272 rc += *it;
273 }
274 }
275 }
276 return rc;
277 }
278
279 bool QDesignerPromotion::removePromotedClass(const QString &className, QString *errorMessage) {
280 // check if it exists and is promoted
281 WidgetDataBase *widgetDataBase = qobject_cast<WidgetDataBase *>(object: m_core->widgetDataBase());
282 if (!widgetDataBase) {
283 *errorMessage = QCoreApplication::tr(s: "The class %1 cannot be removed").arg(a: className);
284 return false;
285 }
286
287 const int index = promotedWidgetDataBaseIndex(widgetDataBase, className, errorMessage);
288 if (index == -1)
289 return false;
290
291 if (referencedPromotedClassNames().contains(value: className)) {
292 *errorMessage = QCoreApplication::tr(s: "The class %1 cannot be removed because it is still referenced.").arg(a: className);
293 return false;
294 }
295 // QTBUG-52963: Check for classes that specify the to-be-removed class as
296 // base class of a promoted class. This should not happen in the normal case
297 // as promoted classes cannot serve as base for further promotion. It is possible
298 // though if a class provided by a plugin (say Qt WebKit's QWebView) is used as
299 // a base class for a promoted widget B and the plugin is removed in the next
300 // launch. QWebView will then appear as promoted class itself and the promoted
301 // class B will depend on it. When removing QWebView, the base class of B will
302 // be changed to that of QWebView by the below code.
303 const PromotedClasses promotedList = promotedClasses();
304 for (PromotedClasses::const_iterator it = promotedList.constBegin(), end = promotedList.constEnd(); it != end; ++it) {
305 if (it->baseItem->name() == className) {
306 const QString extends = widgetDataBase->item(index)->extends();
307 qWarning().nospace() << "Warning: Promoted class " << it->promotedItem->name()
308 << " extends " << className << ", changing its base class to " << extends << '.';
309 it->promotedItem->setExtends(extends);
310 }
311 }
312 widgetDataBase->remove(index);
313 markFormsDirty(core: m_core);
314 return true;
315 }
316
317 bool QDesignerPromotion::changePromotedClassName(const QString &oldclassName, const QString &newClassName, QString *errorMessage) {
318 const MetaDataBase *metaDataBase = qobject_cast<const MetaDataBase*>(object: m_core->metaDataBase());
319 if (!metaDataBase) {
320 *errorMessage = QCoreApplication::tr(s: "The class %1 cannot be renamed").arg(a: oldclassName);
321 return false;
322 }
323 QDesignerWidgetDataBaseInterface *widgetDataBase = m_core->widgetDataBase();
324
325 // check the new name
326 if (newClassName.isEmpty()) {
327 *errorMessage = QCoreApplication::tr(s: "The class %1 cannot be renamed to an empty name.").arg(a: oldclassName);
328 return false;
329 }
330 const int existingIndex = widgetDataBase->indexOfClassName(className: newClassName);
331 if (existingIndex != -1) {
332 *errorMessage = QCoreApplication::tr(s: "There is already a class named %1.").arg(a: newClassName);
333 return false;
334 }
335 // Check old class
336 QDesignerWidgetDataBaseItemInterface *dbItem = promotedWidgetDataBaseItem(widgetDataBase, className: oldclassName, errorMessage);
337 if (!dbItem)
338 return false;
339
340 // Change the name in the data base and change all referencing objects in the meta database
341 dbItem->setName(newClassName);
342 bool foundReferences = false;
343 const QObjectList &dbObjects = metaDataBase->objects();
344 for (QObject* object : dbObjects) {
345 MetaDataBaseItem *item = metaDataBase->metaDataBaseItem(object);
346 Q_ASSERT(item);
347 if (item->customClassName() == oldclassName) {
348 item->setCustomClassName(newClassName);
349 foundReferences = true;
350 }
351 }
352 // set state
353 if (foundReferences)
354 refreshObjectInspector();
355
356 markFormsDirty(core: m_core);
357 return true;
358 }
359
360 bool QDesignerPromotion::setPromotedClassIncludeFile(const QString &className, const QString &includeFile, QString *errorMessage) {
361 // check file
362 if (includeFile.isEmpty()) {
363 *errorMessage = QCoreApplication::tr(s: "Cannot set an empty include file.");
364 return false;
365 }
366 // check item
367 QDesignerWidgetDataBaseInterface *widgetDataBase = m_core->widgetDataBase();
368 QDesignerWidgetDataBaseItemInterface *dbItem = promotedWidgetDataBaseItem(widgetDataBase, className, errorMessage);
369 if (!dbItem)
370 return false;
371 if (dbItem->includeFile() != includeFile) {
372 dbItem->setIncludeFile(includeFile);
373 markFormsDirty(core: m_core);
374 }
375 return true;
376 }
377
378 void QDesignerPromotion::refreshObjectInspector() {
379 if (QDesignerFormWindowManagerInterface *fwm = m_core->formWindowManager()) {
380 if (QDesignerFormWindowInterface *fw = fwm->activeFormWindow())
381 if ( QDesignerObjectInspectorInterface *oi = m_core->objectInspector()) {
382 oi->setFormWindow(fw);
383 }
384 }
385 }
386}
387
388QT_END_NAMESPACE
389

source code of qttools/src/designer/src/lib/shared/qdesigner_promotion.cpp