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 | // designer |
30 | #include "qdesigner.h" |
31 | #include "qdesigner_actions.h" |
32 | #include "qdesigner_server.h" |
33 | #include "qdesigner_settings.h" |
34 | #include "qdesigner_workbench.h" |
35 | #include "mainwindow.h" |
36 | |
37 | #include <qdesigner_propertysheet_p.h> |
38 | |
39 | #include <QtGui/qevent.h> |
40 | #include <QtWidgets/qmessagebox.h> |
41 | #include <QtGui/qicon.h> |
42 | #include <QtWidgets/qerrormessage.h> |
43 | #include <QtCore/qmetaobject.h> |
44 | #include <QtCore/qfile.h> |
45 | #include <QtCore/qlibraryinfo.h> |
46 | #include <QtCore/qlocale.h> |
47 | #include <QtCore/qtimer.h> |
48 | #include <QtCore/qtranslator.h> |
49 | #include <QtCore/qfileinfo.h> |
50 | #include <QtCore/qdebug.h> |
51 | #include <QtCore/qcommandlineparser.h> |
52 | #include <QtCore/qcommandlineoption.h> |
53 | |
54 | #include <QtDesigner/QDesignerComponents> |
55 | |
56 | QT_BEGIN_NAMESPACE |
57 | |
58 | static const char *designerApplicationName = "Designer" ; |
59 | static const char designerDisplayName[] = "Qt Designer" ; |
60 | static const char *designerWarningPrefix = "Designer: " ; |
61 | static QtMessageHandler previousMessageHandler = nullptr; |
62 | |
63 | static void designerMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) |
64 | { |
65 | // Only Designer warnings are displayed as box |
66 | QDesigner *designerApp = qDesigner; |
67 | if (type != QtWarningMsg || !designerApp || !msg.startsWith(s: QLatin1String(designerWarningPrefix))) { |
68 | previousMessageHandler(type, context, msg); |
69 | return; |
70 | } |
71 | designerApp->showErrorMessage(message: msg); |
72 | } |
73 | |
74 | QDesigner::QDesigner(int &argc, char **argv) |
75 | : QApplication(argc, argv), |
76 | m_server(nullptr), |
77 | m_client(nullptr), |
78 | m_workbench(0), m_suppressNewFormShow(false) |
79 | { |
80 | setOrganizationName(QStringLiteral("QtProject" )); |
81 | QGuiApplication::setApplicationDisplayName(QLatin1String(designerDisplayName)); |
82 | setApplicationName(QLatin1String(designerApplicationName)); |
83 | QDesignerComponents::initializeResources(); |
84 | |
85 | #if !defined(Q_OS_OSX) && !defined(Q_OS_WIN) |
86 | setWindowIcon(QIcon(QStringLiteral(":/qt-project.org/designer/images/designer.png" ))); |
87 | #endif |
88 | } |
89 | |
90 | QDesigner::~QDesigner() |
91 | { |
92 | delete m_workbench; |
93 | delete m_server; |
94 | delete m_client; |
95 | } |
96 | |
97 | void QDesigner::showErrorMessage(const QString &message) |
98 | { |
99 | // strip the prefix |
100 | const QString qMessage = |
101 | message.right(n: message.size() - int(qstrlen(str: designerWarningPrefix))); |
102 | // If there is no main window yet, just store the message. |
103 | // The QErrorMessage would otherwise be hidden by the main window. |
104 | if (m_mainWindow) { |
105 | showErrorMessageBox(qMessage); |
106 | } else { |
107 | const QMessageLogContext emptyContext; |
108 | previousMessageHandler(QtWarningMsg, emptyContext, message); // just in case we crash |
109 | m_initializationErrors += qMessage; |
110 | m_initializationErrors += QLatin1Char('\n'); |
111 | } |
112 | } |
113 | |
114 | void QDesigner::showErrorMessageBox(const QString &msg) |
115 | { |
116 | // Manually suppress consecutive messages. |
117 | // This happens if for example sth is wrong with custom widget creation. |
118 | // The same warning will be displayed by Widget box D&D and form Drop |
119 | // while trying to create instance. |
120 | if (m_errorMessageDialog && m_lastErrorMessage == msg) |
121 | return; |
122 | |
123 | if (!m_errorMessageDialog) { |
124 | m_lastErrorMessage.clear(); |
125 | m_errorMessageDialog = new QErrorMessage(m_mainWindow); |
126 | const QString title = QCoreApplication::translate(context: "QDesigner" , key: "%1 - warning" ).arg(a: QLatin1String(designerApplicationName)); |
127 | m_errorMessageDialog->setWindowTitle(title); |
128 | m_errorMessageDialog->setMinimumSize(QSize(600, 250)); |
129 | m_errorMessageDialog->setWindowFlags(m_errorMessageDialog->windowFlags() & ~Qt::WindowContextHelpButtonHint); |
130 | } |
131 | m_errorMessageDialog->showMessage(message: msg); |
132 | m_lastErrorMessage = msg; |
133 | } |
134 | |
135 | QDesignerWorkbench *QDesigner::workbench() const |
136 | { |
137 | return m_workbench; |
138 | } |
139 | |
140 | QDesignerServer *QDesigner::server() const |
141 | { |
142 | return m_server; |
143 | } |
144 | |
145 | static void showHelp(QCommandLineParser &parser, const QString &errorMessage = QString()) |
146 | { |
147 | QString text; |
148 | QTextStream str(&text); |
149 | str << "<html><head/><body>" ; |
150 | if (!errorMessage.isEmpty()) |
151 | str << "<p>" << errorMessage << "</p>" ; |
152 | str << "<pre>" << parser.helpText().toHtmlEscaped() << "</pre></body></html>" ; |
153 | QMessageBox box(errorMessage.isEmpty() ? QMessageBox::Information : QMessageBox::Warning, |
154 | QGuiApplication::applicationDisplayName(), text, |
155 | QMessageBox::Ok); |
156 | box.setTextInteractionFlags(Qt::TextBrowserInteraction); |
157 | box.exec(); |
158 | } |
159 | |
160 | struct Options |
161 | { |
162 | QStringList files; |
163 | QString resourceDir{QLibraryInfo::location(QLibraryInfo::TranslationsPath)}; |
164 | bool server{false}; |
165 | quint16 clientPort{0}; |
166 | bool enableInternalDynamicProperties{false}; |
167 | }; |
168 | |
169 | static inline QDesigner::ParseArgumentsResult |
170 | parseDesignerCommandLineArguments(QCommandLineParser &parser, Options *options, |
171 | QString *errorMessage) |
172 | { |
173 | parser.setApplicationDescription(QStringLiteral("Qt Designer " ) |
174 | + QLatin1String(QT_VERSION_STR) |
175 | + QLatin1String("\n\nUI designer for QWidget-based applications." )); |
176 | const QCommandLineOption helpOption = parser.addHelpOption(); |
177 | parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions); |
178 | const QCommandLineOption serverOption(QStringLiteral("server" ), |
179 | QStringLiteral("Server mode" )); |
180 | parser.addOption(commandLineOption: serverOption); |
181 | const QCommandLineOption clientOption(QStringLiteral("client" ), |
182 | QStringLiteral("Client mode" ), |
183 | QStringLiteral("port" )); |
184 | parser.addOption(commandLineOption: clientOption); |
185 | const QCommandLineOption resourceDirOption(QStringLiteral("resourcedir" ), |
186 | QStringLiteral("Resource directory" ), |
187 | QStringLiteral("directory" )); |
188 | parser.addOption(commandLineOption: resourceDirOption); |
189 | const QCommandLineOption internalDynamicPropertyOption(QStringLiteral("enableinternaldynamicproperties" ), |
190 | QStringLiteral("Enable internal dynamic properties" )); |
191 | parser.addOption(commandLineOption: internalDynamicPropertyOption); |
192 | const QCommandLineOption noScalingOption(QStringLiteral("no-scaling" ), |
193 | QStringLiteral("Disable High DPI scaling" )); |
194 | parser.addOption(commandLineOption: noScalingOption); |
195 | |
196 | parser.addPositionalArgument(QStringLiteral("files" ), |
197 | QStringLiteral("The UI files to open." )); |
198 | |
199 | if (!parser.parse(arguments: QCoreApplication::arguments())) { |
200 | *errorMessage = parser.errorText(); |
201 | return QDesigner::ParseArgumentsError; |
202 | } |
203 | |
204 | if (parser.isSet(option: helpOption)) |
205 | return QDesigner::ParseArgumentsHelpRequested; |
206 | options->server = parser.isSet(option: serverOption); |
207 | if (parser.isSet(option: clientOption)) { |
208 | bool ok; |
209 | options->clientPort = parser.value(option: clientOption).toUShort(ok: &ok); |
210 | if (!ok) { |
211 | *errorMessage = QStringLiteral("Non-numeric argument specified for -client" ); |
212 | return QDesigner::ParseArgumentsError; |
213 | } |
214 | } |
215 | if (parser.isSet(option: resourceDirOption)) |
216 | options->resourceDir = parser.value(option: resourceDirOption); |
217 | options->enableInternalDynamicProperties = parser.isSet(option: internalDynamicPropertyOption); |
218 | options->files = parser.positionalArguments(); |
219 | return QDesigner::ParseArgumentsSuccess; |
220 | } |
221 | |
222 | QDesigner::ParseArgumentsResult QDesigner::parseCommandLineArguments() |
223 | { |
224 | QString errorMessage; |
225 | Options options; |
226 | QCommandLineParser parser; |
227 | const ParseArgumentsResult result = parseDesignerCommandLineArguments(parser, options: &options, errorMessage: &errorMessage); |
228 | if (result != ParseArgumentsSuccess) { |
229 | showHelp(parser, errorMessage); |
230 | return result; |
231 | } |
232 | // initialize the sub components |
233 | if (options.clientPort) |
234 | m_client = new QDesignerClient(options.clientPort, this); |
235 | if (options.server) { |
236 | m_server = new QDesignerServer(); |
237 | printf(format: "%d\n" , m_server->serverPort()); |
238 | fflush(stdout); |
239 | } |
240 | if (options.enableInternalDynamicProperties) |
241 | QDesignerPropertySheet::setInternalDynamicPropertiesEnabled(true); |
242 | |
243 | const QString localSysName = QLocale::system().name(); |
244 | QScopedPointer<QTranslator> designerTranslator(new QTranslator(this)); |
245 | if (designerTranslator->load(QStringLiteral("designer_" ) + localSysName, directory: options.resourceDir)) { |
246 | installTranslator(messageFile: designerTranslator.take()); |
247 | QScopedPointer<QTranslator> qtTranslator(new QTranslator(this)); |
248 | if (qtTranslator->load(QStringLiteral("qt_" ) + localSysName, directory: options.resourceDir)) |
249 | installTranslator(messageFile: qtTranslator.take()); |
250 | } |
251 | |
252 | m_workbench = new QDesignerWorkbench(); |
253 | |
254 | emit initialized(); |
255 | previousMessageHandler = qInstallMessageHandler(designerMessageHandler); // Warn when loading faulty forms |
256 | Q_ASSERT(previousMessageHandler); |
257 | |
258 | m_suppressNewFormShow = m_workbench->readInBackup(); |
259 | |
260 | if (!options.files.isEmpty()) { |
261 | const QStringList::const_iterator cend = options.files.constEnd(); |
262 | for (QStringList::const_iterator it = options.files.constBegin(); it != cend; ++it) { |
263 | // Ensure absolute paths for recent file list to be unique |
264 | QString fileName = *it; |
265 | const QFileInfo fi(fileName); |
266 | if (fi.exists() && fi.isRelative()) |
267 | fileName = fi.absoluteFilePath(); |
268 | m_workbench->readInForm(fileName); |
269 | } |
270 | } |
271 | if ( m_workbench->formWindowCount()) |
272 | m_suppressNewFormShow = true; |
273 | |
274 | // Show up error box with parent now if something went wrong |
275 | if (m_initializationErrors.isEmpty()) { |
276 | if (!m_suppressNewFormShow && QDesignerSettings(m_workbench->core()).showNewFormOnStartup()) |
277 | QTimer::singleShot(interval: 100, receiver: this, slot: &QDesigner::callCreateForm); // won't show anything if suppressed |
278 | } else { |
279 | showErrorMessageBox(msg: m_initializationErrors); |
280 | m_initializationErrors.clear(); |
281 | } |
282 | return result; |
283 | } |
284 | |
285 | bool QDesigner::event(QEvent *ev) |
286 | { |
287 | bool eaten; |
288 | switch (ev->type()) { |
289 | case QEvent::FileOpen: |
290 | // Set it true first since, if it's a Qt 3 form, the messagebox from convert will fire the timer. |
291 | m_suppressNewFormShow = true; |
292 | if (!m_workbench->readInForm(fileName: static_cast<QFileOpenEvent *>(ev)->file())) |
293 | m_suppressNewFormShow = false; |
294 | eaten = true; |
295 | break; |
296 | case QEvent::Close: { |
297 | QCloseEvent *closeEvent = static_cast<QCloseEvent *>(ev); |
298 | closeEvent->setAccepted(m_workbench->handleClose()); |
299 | if (closeEvent->isAccepted()) { |
300 | // We're going down, make sure that we don't get our settings saved twice. |
301 | if (m_mainWindow) |
302 | m_mainWindow->setCloseEventPolicy(MainWindowBase::AcceptCloseEvents); |
303 | eaten = QApplication::event(ev); |
304 | } |
305 | eaten = true; |
306 | break; |
307 | } |
308 | default: |
309 | eaten = QApplication::event(ev); |
310 | break; |
311 | } |
312 | return eaten; |
313 | } |
314 | |
315 | void QDesigner::setMainWindow(MainWindowBase *tw) |
316 | { |
317 | m_mainWindow = tw; |
318 | } |
319 | |
320 | MainWindowBase *QDesigner::mainWindow() const |
321 | { |
322 | return m_mainWindow; |
323 | } |
324 | |
325 | void QDesigner::callCreateForm() |
326 | { |
327 | if (!m_suppressNewFormShow) |
328 | m_workbench->actionManager()->createForm(); |
329 | } |
330 | |
331 | QT_END_NAMESPACE |
332 | |