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 "mainwindow.h" |
30 | #include "qdesigner.h" |
31 | #include "qdesigner_actions.h" |
32 | #include "qdesigner_workbench.h" |
33 | #include "qdesigner_formwindow.h" |
34 | #include "qdesigner_toolwindow.h" |
35 | #include "qdesigner_settings.h" |
36 | #include "qttoolbardialog.h" |
37 | |
38 | #include <QtDesigner/abstractformwindow.h> |
39 | |
40 | #include <QtWidgets/qaction.h> |
41 | #include <QtGui/qevent.h> |
42 | #include <QtWidgets/qtoolbar.h> |
43 | #include <QtWidgets/qmdisubwindow.h> |
44 | #include <QtWidgets/qstatusbar.h> |
45 | #include <QtWidgets/qmenu.h> |
46 | #include <QtWidgets/qlayout.h> |
47 | #include <QtWidgets/qdockwidget.h> |
48 | |
49 | #include <QtCore/qurl.h> |
50 | #include <QtCore/qdebug.h> |
51 | #include <QtCore/qmimedata.h> |
52 | |
53 | #include <algorithm> |
54 | |
55 | static const char *uriListMimeFormatC = "text/uri-list" ; |
56 | |
57 | QT_BEGIN_NAMESPACE |
58 | |
59 | using ActionList = QList<QAction *>; |
60 | |
61 | // Helpers for creating toolbars and menu |
62 | |
63 | static void addActionsToToolBar(const ActionList &actions, QToolBar *t) |
64 | { |
65 | for (QAction *action : actions) { |
66 | if (action->property(name: QDesignerActions::defaultToolbarPropertyName).toBool()) |
67 | t->addAction(action); |
68 | } |
69 | } |
70 | static QToolBar *createToolBar(const QString &title, const QString &objectName, const ActionList &actions) |
71 | { |
72 | QToolBar *rc = new QToolBar; |
73 | rc->setObjectName(objectName); |
74 | rc->setWindowTitle(title); |
75 | addActionsToToolBar(actions, t: rc); |
76 | return rc; |
77 | } |
78 | |
79 | // ---------------- MainWindowBase |
80 | |
81 | MainWindowBase::MainWindowBase(QWidget *parent, Qt::WindowFlags flags) : |
82 | QMainWindow(parent, flags) |
83 | { |
84 | #ifndef Q_OS_MACOS |
85 | setWindowIcon(qDesigner->windowIcon()); |
86 | #endif |
87 | } |
88 | |
89 | void MainWindowBase::closeEvent(QCloseEvent *e) |
90 | { |
91 | switch (m_policy) { |
92 | case AcceptCloseEvents: |
93 | QMainWindow::closeEvent(event: e); |
94 | break; |
95 | case EmitCloseEventSignal: |
96 | emit closeEventReceived(e); |
97 | break; |
98 | } |
99 | } |
100 | |
101 | QVector<QToolBar *> MainWindowBase::createToolBars(const QDesignerActions *actions, bool singleToolBar) |
102 | { |
103 | // Note that whenever you want to add a new tool bar here, you also have to update the default |
104 | // action groups added to the toolbar manager in the mainwindow constructor |
105 | QVector<QToolBar *> rc; |
106 | if (singleToolBar) { |
107 | //: Not currently used (main tool bar) |
108 | QToolBar *main = createToolBar(title: tr(s: "Main" ), QStringLiteral("mainToolBar" ), actions: actions->fileActions()->actions()); |
109 | addActionsToToolBar(actions: actions->editActions()->actions(), t: main); |
110 | addActionsToToolBar(actions: actions->toolActions()->actions(), t: main); |
111 | addActionsToToolBar(actions: actions->formActions()->actions(), t: main); |
112 | rc.push_back(t: main); |
113 | } else { |
114 | rc.push_back(t: createToolBar(title: tr(s: "File" ), QStringLiteral("fileToolBar" ), actions: actions->fileActions()->actions())); |
115 | rc.push_back(t: createToolBar(title: tr(s: "Edit" ), QStringLiteral("editToolBar" ), actions: actions->editActions()->actions())); |
116 | rc.push_back(t: createToolBar(title: tr(s: "Tools" ), QStringLiteral("toolsToolBar" ), actions: actions->toolActions()->actions())); |
117 | rc.push_back(t: createToolBar(title: tr(s: "Form" ), QStringLiteral("formToolBar" ), actions: actions->formActions()->actions())); |
118 | } |
119 | return rc; |
120 | } |
121 | |
122 | QString MainWindowBase::mainWindowTitle() |
123 | { |
124 | return tr(s: "Qt Designer" ); |
125 | } |
126 | |
127 | // Use the minor Qt version as settings versions to avoid conflicts |
128 | int MainWindowBase::settingsVersion() |
129 | { |
130 | const int version = QT_VERSION; |
131 | return (version & 0x00FF00) >> 8; |
132 | } |
133 | |
134 | // ----------------- DockedMdiArea |
135 | |
136 | DockedMdiArea::DockedMdiArea(const QString &extension, QWidget *parent) : |
137 | QMdiArea(parent), |
138 | m_extension(extension) |
139 | { |
140 | setFrameStyle(QFrame::StyledPanel | QFrame::Sunken); |
141 | setLineWidth(1); |
142 | setAcceptDrops(true); |
143 | setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); |
144 | setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); |
145 | } |
146 | |
147 | QStringList DockedMdiArea::uiFiles(const QMimeData *d) const |
148 | { |
149 | // Extract dropped UI files from Mime data. |
150 | QStringList rc; |
151 | if (!d->hasFormat(mimetype: QLatin1String(uriListMimeFormatC))) |
152 | return rc; |
153 | const auto urls = d->urls(); |
154 | if (urls.isEmpty()) |
155 | return rc; |
156 | for (const auto &url : urls) { |
157 | const QString fileName = url.toLocalFile(); |
158 | if (!fileName.isEmpty() && fileName.endsWith(s: m_extension)) |
159 | rc.push_back(t: fileName); |
160 | } |
161 | return rc; |
162 | } |
163 | |
164 | bool DockedMdiArea::event(QEvent *event) |
165 | { |
166 | // Listen for desktop file manager drop and emit a signal once a file is |
167 | // dropped. |
168 | switch (event->type()) { |
169 | case QEvent::DragEnter: { |
170 | QDragEnterEvent *e = static_cast<QDragEnterEvent*>(event); |
171 | if (!uiFiles(d: e->mimeData()).isEmpty()) { |
172 | e->acceptProposedAction(); |
173 | return true; |
174 | } |
175 | } |
176 | break; |
177 | case QEvent::Drop: { |
178 | QDropEvent *e = static_cast<QDropEvent*>(event); |
179 | const QStringList files = uiFiles(d: e->mimeData()); |
180 | const QStringList::const_iterator cend = files.constEnd(); |
181 | for (QStringList::const_iterator it = files.constBegin(); it != cend; ++it) { |
182 | emit fileDropped(*it); |
183 | } |
184 | e->acceptProposedAction(); |
185 | return true; |
186 | } |
187 | break; |
188 | default: |
189 | break; |
190 | } |
191 | return QMdiArea::event(event); |
192 | } |
193 | |
194 | // ------------- ToolBarManager: |
195 | |
196 | static void addActionsToToolBarManager(const ActionList &al, const QString &title, QtToolBarManager *tbm) |
197 | { |
198 | for (QAction *action : al) |
199 | tbm->addAction(action, category: title); |
200 | } |
201 | |
202 | ToolBarManager::ToolBarManager(QMainWindow *configureableMainWindow, |
203 | QWidget *parent, |
204 | QMenu *, |
205 | const QDesignerActions *actions, |
206 | const QVector<QToolBar *> &toolbars, |
207 | const QVector<QDesignerToolWindow *> &toolWindows) : |
208 | QObject(parent), |
209 | m_configureableMainWindow(configureableMainWindow), |
210 | m_parent(parent), |
211 | m_toolBarMenu(toolBarMenu), |
212 | m_manager(new QtToolBarManager(this)), |
213 | m_configureAction(new QAction(tr(s: "Configure Toolbars..." ), this)), |
214 | m_toolbars(toolbars) |
215 | { |
216 | m_configureAction->setMenuRole(QAction::NoRole); |
217 | m_configureAction->setObjectName(QStringLiteral("__qt_configure_tool_bars_action" )); |
218 | connect(sender: m_configureAction, signal: &QAction::triggered, receiver: this, slot: &ToolBarManager::configureToolBars); |
219 | |
220 | m_manager->setMainWindow(configureableMainWindow); |
221 | |
222 | for (QToolBar *tb : qAsConst(t&: m_toolbars)) { |
223 | const QString title = tb->windowTitle(); |
224 | m_manager->addToolBar(toolBar: tb, category: title); |
225 | addActionsToToolBarManager(al: tb->actions(), title, tbm: m_manager); |
226 | } |
227 | |
228 | addActionsToToolBarManager(al: actions->windowActions()->actions(), title: tr(s: "Window" ), tbm: m_manager); |
229 | addActionsToToolBarManager(al: actions->helpActions()->actions(), title: tr(s: "Help" ), tbm: m_manager); |
230 | |
231 | // Filter out the device profile preview actions which have int data(). |
232 | ActionList previewActions = actions->styleActions()->actions(); |
233 | ActionList::iterator it = previewActions.begin(); |
234 | for ( ; (*it)->isSeparator() || (*it)->data().type() == QVariant::Int; ++it) ; |
235 | previewActions.erase(afirst: previewActions.begin(), alast: it); |
236 | addActionsToToolBarManager(al: previewActions, title: tr(s: "Style" ), tbm: m_manager); |
237 | |
238 | const QString dockTitle = tr(s: "Dock views" ); |
239 | for (QDesignerToolWindow *tw : toolWindows) { |
240 | if (QAction *action = tw->action()) |
241 | m_manager->addAction(action, category: dockTitle); |
242 | } |
243 | |
244 | addActionsToToolBarManager(al: actions->fileActions()->actions(), title: tr(s: "File" ), tbm: m_manager); |
245 | addActionsToToolBarManager(al: actions->editActions()->actions(), title: tr(s: "Edit" ), tbm: m_manager); |
246 | addActionsToToolBarManager(al: actions->toolActions()->actions(), title: tr(s: "Tools" ), tbm: m_manager); |
247 | addActionsToToolBarManager(al: actions->formActions()->actions(), title: tr(s: "Form" ), tbm: m_manager); |
248 | |
249 | m_manager->addAction(action: m_configureAction, category: tr(s: "Toolbars" )); |
250 | updateToolBarMenu(); |
251 | } |
252 | |
253 | // sort function for sorting tool bars alphabetically by title [non-static since called from template] |
254 | |
255 | bool toolBarTitleLessThan(const QToolBar *t1, const QToolBar *t2) |
256 | { |
257 | return t1->windowTitle() < t2->windowTitle(); |
258 | } |
259 | |
260 | void ToolBarManager::() |
261 | { |
262 | // Sort tool bars alphabetically by title |
263 | std::stable_sort(first: m_toolbars.begin(), last: m_toolbars.end(), comp: toolBarTitleLessThan); |
264 | // add to menu |
265 | m_toolBarMenu->clear(); |
266 | for (QToolBar *tb : qAsConst(t&: m_toolbars)) |
267 | m_toolBarMenu->addAction(action: tb->toggleViewAction()); |
268 | m_toolBarMenu->addAction(action: m_configureAction); |
269 | } |
270 | |
271 | void ToolBarManager::configureToolBars() |
272 | { |
273 | QtToolBarDialog dlg(m_parent); |
274 | dlg.setWindowFlags(dlg.windowFlags() & ~Qt::WindowContextHelpButtonHint); |
275 | dlg.setToolBarManager(m_manager); |
276 | dlg.exec(); |
277 | updateToolBarMenu(); |
278 | } |
279 | |
280 | QByteArray ToolBarManager::saveState(int version) const |
281 | { |
282 | return m_manager->saveState(version); |
283 | } |
284 | |
285 | bool ToolBarManager::restoreState(const QByteArray &state, int version) |
286 | { |
287 | return m_manager->restoreState(state, version); |
288 | } |
289 | |
290 | // ---------- DockedMainWindow |
291 | |
292 | DockedMainWindow::DockedMainWindow(QDesignerWorkbench *wb, |
293 | QMenu *, |
294 | const QVector<QDesignerToolWindow *> &toolWindows) : |
295 | m_toolBarManager(nullptr) |
296 | { |
297 | setObjectName(QStringLiteral("MDIWindow" )); |
298 | setWindowTitle(mainWindowTitle()); |
299 | |
300 | const QVector<QToolBar *> toolbars = createToolBars(actions: wb->actionManager(), singleToolBar: false); |
301 | for (QToolBar *tb : toolbars) |
302 | addToolBar(toolbar: tb); |
303 | DockedMdiArea *dma = new DockedMdiArea(wb->actionManager()->uiExtension()); |
304 | connect(sender: dma, signal: &DockedMdiArea::fileDropped, |
305 | receiver: this, slot: &DockedMainWindow::fileDropped); |
306 | connect(sender: dma, signal: &QMdiArea::subWindowActivated, |
307 | receiver: this, slot: &DockedMainWindow::slotSubWindowActivated); |
308 | setCentralWidget(dma); |
309 | |
310 | QStatusBar *sb = statusBar(); |
311 | Q_UNUSED(sb); |
312 | |
313 | m_toolBarManager = new ToolBarManager(this, this, toolBarMenu, wb->actionManager(), toolbars, toolWindows); |
314 | } |
315 | |
316 | QMdiArea *DockedMainWindow::mdiArea() const |
317 | { |
318 | return static_cast<QMdiArea *>(centralWidget()); |
319 | } |
320 | |
321 | void DockedMainWindow::slotSubWindowActivated(QMdiSubWindow* subWindow) |
322 | { |
323 | if (subWindow) { |
324 | QWidget *widget = subWindow->widget(); |
325 | if (QDesignerFormWindow *fw = qobject_cast<QDesignerFormWindow*>(object: widget)) { |
326 | emit formWindowActivated(fw); |
327 | mdiArea()->setActiveSubWindow(subWindow); |
328 | } |
329 | } |
330 | } |
331 | |
332 | // Create a MDI subwindow for the form. |
333 | QMdiSubWindow *DockedMainWindow::createMdiSubWindow(QWidget *fw, Qt::WindowFlags f, const QKeySequence &designerCloseActionShortCut) |
334 | { |
335 | QMdiSubWindow *rc = mdiArea()->addSubWindow(widget: fw, flags: f); |
336 | // Make action shortcuts respond only if focused to avoid conflicts with |
337 | // designer menu actions |
338 | if (designerCloseActionShortCut == QKeySequence(QKeySequence::Close)) { |
339 | const ActionList = rc->systemMenu()->actions(); |
340 | if (!systemMenuActions.isEmpty()) { |
341 | const ActionList::const_iterator cend = systemMenuActions.constEnd(); |
342 | for (ActionList::const_iterator it = systemMenuActions.constBegin(); it != cend; ++it) { |
343 | if ( (*it)->shortcut() == designerCloseActionShortCut) { |
344 | (*it)->setShortcutContext(Qt::WidgetShortcut); |
345 | break; |
346 | } |
347 | } |
348 | } |
349 | } |
350 | return rc; |
351 | } |
352 | |
353 | DockedMainWindow::DockWidgetList DockedMainWindow::addToolWindows(const DesignerToolWindowList &tls) |
354 | { |
355 | DockWidgetList rc; |
356 | for (QDesignerToolWindow *tw : tls) { |
357 | QDockWidget *dockWidget = new QDockWidget; |
358 | dockWidget->setObjectName(tw->objectName() + QStringLiteral("_dock" )); |
359 | dockWidget->setWindowTitle(tw->windowTitle()); |
360 | addDockWidget(area: tw->dockWidgetAreaHint(), dockwidget: dockWidget); |
361 | dockWidget->setWidget(tw); |
362 | rc.push_back(t: dockWidget); |
363 | } |
364 | return rc; |
365 | } |
366 | |
367 | // Settings consist of MainWindow state and tool bar manager state |
368 | void DockedMainWindow::restoreSettings(const QDesignerSettings &s, const DockWidgetList &dws, const QRect &desktopArea) |
369 | { |
370 | const int version = settingsVersion(); |
371 | m_toolBarManager->restoreState(state: s.toolBarsState(mode: DockedMode), version); |
372 | |
373 | // If there are no old geometry settings, show the window maximized |
374 | s.restoreGeometry(w: this, fallBack: QRect(desktopArea.topLeft(), QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX))); |
375 | |
376 | const QByteArray mainWindowState = s.mainWindowState(mode: DockedMode); |
377 | const bool restored = !mainWindowState.isEmpty() && restoreState(state: mainWindowState, version); |
378 | if (!restored) { |
379 | // Default: Tabify less relevant windows bottom/right. |
380 | tabifyDockWidget(first: dws.at(i: QDesignerToolWindow::SignalSlotEditor), |
381 | second: dws.at(i: QDesignerToolWindow::ActionEditor)); |
382 | tabifyDockWidget(first: dws.at(i: QDesignerToolWindow::ActionEditor), |
383 | second: dws.at(i: QDesignerToolWindow::ResourceEditor)); |
384 | } |
385 | } |
386 | |
387 | void DockedMainWindow::saveSettings(QDesignerSettings &s) const |
388 | { |
389 | const int version = settingsVersion(); |
390 | s.setToolBarsState(mode: DockedMode, mainWindowState: m_toolBarManager->saveState(version)); |
391 | s.saveGeometryFor(w: this); |
392 | s.setMainWindowState(mode: DockedMode, mainWindowState: saveState(version)); |
393 | } |
394 | |
395 | QT_END_NAMESPACE |
396 | |