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 demonstration applications of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:BSD$
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** BSD License Usage
18** Alternatively, you may use this file under the terms of the BSD license
19** as follows:
20**
21** "Redistribution and use in source and binary forms, with or without
22** modification, are permitted provided that the following conditions are
23** met:
24** * Redistributions of source code must retain the above copyright
25** notice, this list of conditions and the following disclaimer.
26** * Redistributions in binary form must reproduce the above copyright
27** notice, this list of conditions and the following disclaimer in
28** the documentation and/or other materials provided with the
29** distribution.
30** * Neither the name of The Qt Company Ltd nor the names of its
31** contributors may be used to endorse or promote products derived
32** from this software without specific prior written permission.
33**
34**
35** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
36** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
37** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
38** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
39** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
42** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
43** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
44** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
45** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
46**
47** $QT_END_LICENSE$
48**
49****************************************************************************/
50
51#include "mainwindow.h"
52#include "colorswatch.h"
53#include "toolbar.h"
54
55#include <QAction>
56#include <QLayout>
57#include <QMenu>
58#include <QMenuBar>
59#include <QStatusBar>
60#include <QTextEdit>
61#include <QFile>
62#include <QDataStream>
63#include <QFileDialog>
64#include <QDialogButtonBox>
65#include <QMessageBox>
66#include <QApplication>
67#include <QPainter>
68#include <QMouseEvent>
69#include <QLineEdit>
70#include <QComboBox>
71#include <QLabel>
72#include <QPushButton>
73#include <QTextEdit>
74#include <QDebug>
75
76static const char message[] =
77 "<p><b>Qt Main Window Example</b></p>"
78
79 "<p>This is a demonstration of the QMainWindow, QToolBar and "
80 "QDockWidget classes.</p>"
81
82 "<p>The tool bar and dock widgets can be dragged around and rearranged "
83 "using the mouse or via the menu.</p>"
84
85 "<p>Each dock widget contains a colored frame and a context "
86 "(right-click) menu.</p>"
87
88#ifdef Q_OS_MAC
89 "<p>On OS X, the \"Black\" dock widget has been created as a "
90 "<em>Drawer</em>, which is a special kind of QDockWidget.</p>"
91#endif
92 ;
93
94Q_DECLARE_METATYPE(QDockWidget::DockWidgetFeatures)
95
96MainWindow::MainWindow(const CustomSizeHintMap &customSizeHints,
97 QWidget *parent, Qt::WindowFlags flags)
98 : QMainWindow(parent, flags)
99{
100 Q_UNUSED(message);
101 setObjectName("MainWindow");
102 setWindowTitle("Qt Main Window Example");
103
104 QTextEdit *center = new QTextEdit(this);
105 center->setReadOnly(true);
106 center->setMinimumSize(minw: 400, minh: 205);
107 setCentralWidget(center);
108
109 setupToolBar();
110 setupMenuBar();
111 setupDockWidgets(customSizeHints);
112
113 statusBar()->showMessage(text: tr(s: "Status Bar"));
114}
115
116void MainWindow::actionTriggered(QAction *action)
117{
118 qDebug(msg: "action '%s' triggered", action->text().toLocal8Bit().data());
119}
120
121void MainWindow::setupToolBar()
122{
123#ifdef Q_OS_MACOS
124 setUnifiedTitleAndToolBarOnMac(true);
125#endif
126
127 for (int i = 0; i < 3; ++i) {
128 ToolBar *tb = new ToolBar(QString::fromLatin1(str: "Tool Bar %1").arg(a: i + 1), this);
129 toolBars.append(t: tb);
130 addToolBar(toolbar: tb);
131 }
132}
133
134void MainWindow::setupMenuBar()
135{
136 QMenu *menu = menuBar()->addMenu(title: tr(s: "&File"));
137
138 menu->addAction(text: tr(s: "Save layout..."), object: this, slot: &MainWindow::saveLayout);
139 menu->addAction(text: tr(s: "Load layout..."), object: this, slot: &MainWindow::loadLayout);
140 menu->addAction(text: tr(s: "Switch layout direction"),object: this, slot: &MainWindow::switchLayoutDirection);
141
142 menu->addSeparator();
143 menu->addAction(text: tr(s: "&Quit"), object: this, slot: &QWidget::close);
144
145 mainWindowMenu = menuBar()->addMenu(title: tr(s: "Main window"));
146
147 QAction *action = mainWindowMenu->addAction(text: tr(s: "Animated docks"));
148 action->setCheckable(true);
149 action->setChecked(dockOptions() & AnimatedDocks);
150 connect(sender: action, signal: &QAction::toggled, receiver: this, slot: &MainWindow::setDockOptions);
151
152 action = mainWindowMenu->addAction(text: tr(s: "Allow nested docks"));
153 action->setCheckable(true);
154 action->setChecked(dockOptions() & AllowNestedDocks);
155 connect(sender: action, signal: &QAction::toggled, receiver: this, slot: &MainWindow::setDockOptions);
156
157 action = mainWindowMenu->addAction(text: tr(s: "Allow tabbed docks"));
158 action->setCheckable(true);
159 action->setChecked(dockOptions() & AllowTabbedDocks);
160 connect(sender: action, signal: &QAction::toggled, receiver: this, slot: &MainWindow::setDockOptions);
161
162 action = mainWindowMenu->addAction(text: tr(s: "Force tabbed docks"));
163 action->setCheckable(true);
164 action->setChecked(dockOptions() & ForceTabbedDocks);
165 connect(sender: action, signal: &QAction::toggled, receiver: this, slot: &MainWindow::setDockOptions);
166
167 action = mainWindowMenu->addAction(text: tr(s: "Vertical tabs"));
168 action->setCheckable(true);
169 action->setChecked(dockOptions() & VerticalTabs);
170 connect(sender: action, signal: &QAction::toggled, receiver: this, slot: &MainWindow::setDockOptions);
171
172 action = mainWindowMenu->addAction(text: tr(s: "Grouped dragging"));
173 action->setCheckable(true);
174 action->setChecked(dockOptions() & GroupedDragging);
175 connect(sender: action, signal: &QAction::toggled, receiver: this, slot: &MainWindow::setDockOptions);
176
177 QMenu *toolBarMenu = menuBar()->addMenu(title: tr(s: "Tool bars"));
178 for (int i = 0; i < toolBars.count(); ++i)
179 toolBarMenu->addMenu(menu: toolBars.at(i)->toolbarMenu());
180
181#ifdef Q_OS_MACOS
182 toolBarMenu->addSeparator();
183
184 action = toolBarMenu->addAction(tr("Unified"));
185 action->setCheckable(true);
186 action->setChecked(unifiedTitleAndToolBarOnMac());
187 connect(action, &QAction::toggled, this, &QMainWindow::setUnifiedTitleAndToolBarOnMac);
188#endif
189
190 dockWidgetMenu = menuBar()->addMenu(title: tr(s: "&Dock Widgets"));
191
192 QMenu *aboutMenu = menuBar()->addMenu(title: tr(s: "About"));
193 QAction *aboutAct = aboutMenu->addAction(text: tr(s: "&About"), object: this, slot: &MainWindow::about);
194 aboutAct->setStatusTip(tr(s: "Show the application's About box"));
195
196 QAction *aboutQtAct = aboutMenu->addAction(text: tr(s: "About &Qt"), qApp, slot: &QApplication::aboutQt);
197 aboutQtAct->setStatusTip(tr(s: "Show the Qt library's About box"));
198}
199
200void MainWindow::setDockOptions()
201{
202 DockOptions opts;
203 QList<QAction*> actions = mainWindowMenu->actions();
204
205 if (actions.at(i: 0)->isChecked())
206 opts |= AnimatedDocks;
207 if (actions.at(i: 1)->isChecked())
208 opts |= AllowNestedDocks;
209 if (actions.at(i: 2)->isChecked())
210 opts |= AllowTabbedDocks;
211 if (actions.at(i: 3)->isChecked())
212 opts |= ForceTabbedDocks;
213 if (actions.at(i: 4)->isChecked())
214 opts |= VerticalTabs;
215 if (actions.at(i: 5)->isChecked())
216 opts |= GroupedDragging;
217
218 QMainWindow::setDockOptions(opts);
219}
220
221void MainWindow::saveLayout()
222{
223 QString fileName
224 = QFileDialog::getSaveFileName(parent: this, caption: tr(s: "Save layout"));
225 if (fileName.isEmpty())
226 return;
227 QFile file(fileName);
228 if (!file.open(flags: QFile::WriteOnly)) {
229 QString msg = tr(s: "Failed to open %1\n%2")
230 .arg(args: QDir::toNativeSeparators(pathName: fileName), args: file.errorString());
231 QMessageBox::warning(parent: this, title: tr(s: "Error"), text: msg);
232 return;
233 }
234
235 QByteArray geo_data = saveGeometry();
236 QByteArray layout_data = saveState();
237
238 bool ok = file.putChar(c: (uchar)geo_data.size());
239 if (ok)
240 ok = file.write(data: geo_data) == geo_data.size();
241 if (ok)
242 ok = file.write(data: layout_data) == layout_data.size();
243
244 if (!ok) {
245 QString msg = tr(s: "Error writing to %1\n%2")
246 .arg(args: QDir::toNativeSeparators(pathName: fileName), args: file.errorString());
247 QMessageBox::warning(parent: this, title: tr(s: "Error"), text: msg);
248 return;
249 }
250}
251
252void MainWindow::loadLayout()
253{
254 QString fileName
255 = QFileDialog::getOpenFileName(parent: this, caption: tr(s: "Load layout"));
256 if (fileName.isEmpty())
257 return;
258 QFile file(fileName);
259 if (!file.open(flags: QFile::ReadOnly)) {
260 QString msg = tr(s: "Failed to open %1\n%2")
261 .arg(args: QDir::toNativeSeparators(pathName: fileName), args: file.errorString());
262 QMessageBox::warning(parent: this, title: tr(s: "Error"), text: msg);
263 return;
264 }
265
266 uchar geo_size;
267 QByteArray geo_data;
268 QByteArray layout_data;
269
270 bool ok = file.getChar(c: (char*)&geo_size);
271 if (ok) {
272 geo_data = file.read(maxlen: geo_size);
273 ok = geo_data.size() == geo_size;
274 }
275 if (ok) {
276 layout_data = file.readAll();
277 ok = layout_data.size() > 0;
278 }
279
280 if (ok)
281 ok = restoreGeometry(geometry: geo_data);
282 if (ok)
283 ok = restoreState(state: layout_data);
284
285 if (!ok) {
286 QString msg = tr(s: "Error reading %1").arg(a: QDir::toNativeSeparators(pathName: fileName));
287 QMessageBox::warning(parent: this, title: tr(s: "Error"), text: msg);
288 return;
289 }
290}
291
292static QAction *addCornerAction(const QString &text, QMainWindow *mw, QMenu *menu, QActionGroup *group,
293 Qt::Corner c, Qt::DockWidgetArea a)
294{
295 QAction *result = menu->addAction(text, object: mw, slot: [=]() { mw->setCorner(corner: c, area: a); });
296 result->setCheckable(true);
297 group->addAction(a: result);
298 return result;
299}
300
301void MainWindow::setupDockWidgets(const CustomSizeHintMap &customSizeHints)
302{
303 qRegisterMetaType<QDockWidget::DockWidgetFeatures>();
304
305 QMenu *cornerMenu = dockWidgetMenu->addMenu(title: tr(s: "Top left corner"));
306 QActionGroup *group = new QActionGroup(this);
307 group->setExclusive(true);
308 QAction *cornerAction = addCornerAction(text: tr(s: "Top dock area"), mw: this, menu: cornerMenu, group, c: Qt::TopLeftCorner, a: Qt::TopDockWidgetArea);
309 cornerAction->setChecked(true);
310 addCornerAction(text: tr(s: "Left dock area"), mw: this, menu: cornerMenu, group, c: Qt::TopLeftCorner, a: Qt::LeftDockWidgetArea);
311
312 cornerMenu = dockWidgetMenu->addMenu(title: tr(s: "Top right corner"));
313 group = new QActionGroup(this);
314 group->setExclusive(true);
315 cornerAction = addCornerAction(text: tr(s: "Top dock area"), mw: this, menu: cornerMenu, group, c: Qt::TopRightCorner, a: Qt::TopDockWidgetArea);
316 cornerAction->setChecked(true);
317 addCornerAction(text: tr(s: "Right dock area"), mw: this, menu: cornerMenu, group, c: Qt::TopRightCorner, a: Qt::RightDockWidgetArea);
318
319 cornerMenu = dockWidgetMenu->addMenu(title: tr(s: "Bottom left corner"));
320 group = new QActionGroup(this);
321 group->setExclusive(true);
322 cornerAction = addCornerAction(text: tr(s: "Bottom dock area"), mw: this, menu: cornerMenu, group, c: Qt::BottomLeftCorner, a: Qt::BottomDockWidgetArea);
323 cornerAction->setChecked(true);
324 addCornerAction(text: tr(s: "Left dock area"), mw: this, menu: cornerMenu, group, c: Qt::BottomLeftCorner, a: Qt::LeftDockWidgetArea);
325
326 cornerMenu = dockWidgetMenu->addMenu(title: tr(s: "Bottom right corner"));
327 group = new QActionGroup(this);
328 group->setExclusive(true);
329 cornerAction = addCornerAction(text: tr(s: "Bottom dock area"), mw: this, menu: cornerMenu, group, c: Qt::BottomRightCorner, a: Qt::BottomDockWidgetArea);
330 cornerAction->setChecked(true);
331 addCornerAction(text: tr(s: "Right dock area"), mw: this, menu: cornerMenu, group, c: Qt::BottomRightCorner, a: Qt::RightDockWidgetArea);
332
333 dockWidgetMenu->addSeparator();
334
335 static const struct Set {
336 const char * name;
337 uint flags;
338 Qt::DockWidgetArea area;
339 } sets [] = {
340#ifndef Q_OS_MAC
341 { .name: "Black", .flags: 0, .area: Qt::LeftDockWidgetArea },
342#else
343 { "Black", Qt::Drawer, Qt::LeftDockWidgetArea },
344#endif
345 { .name: "White", .flags: 0, .area: Qt::RightDockWidgetArea },
346 { .name: "Red", .flags: 0, .area: Qt::TopDockWidgetArea },
347 { .name: "Green", .flags: 0, .area: Qt::TopDockWidgetArea },
348 { .name: "Blue", .flags: 0, .area: Qt::BottomDockWidgetArea },
349 { .name: "Yellow", .flags: 0, .area: Qt::BottomDockWidgetArea }
350 };
351 const int setCount = sizeof(sets) / sizeof(Set);
352
353 const QIcon qtIcon(QPixmap(":/res/qt.png"));
354 for (int i = 0; i < setCount; ++i) {
355 ColorSwatch *swatch = new ColorSwatch(tr(s: sets[i].name), this, Qt::WindowFlags(sets[i].flags));
356 if (i % 2)
357 swatch->setWindowIcon(qtIcon);
358 if (qstrcmp(str1: sets[i].name, str2: "Blue") == 0) {
359 BlueTitleBar *titlebar = new BlueTitleBar(swatch);
360 swatch->setTitleBarWidget(titlebar);
361 connect(sender: swatch, signal: &QDockWidget::topLevelChanged, receiver: titlebar, slot: &BlueTitleBar::updateMask);
362 connect(sender: swatch, signal: &QDockWidget::featuresChanged, receiver: titlebar, slot: &BlueTitleBar::updateMask, type: Qt::QueuedConnection);
363 }
364
365 QString name = QString::fromLatin1(str: sets[i].name);
366 if (customSizeHints.contains(akey: name))
367 swatch->setCustomSizeHint(customSizeHints.value(akey: name));
368
369 addDockWidget(area: sets[i].area, dockwidget: swatch);
370 dockWidgetMenu->addMenu(menu: swatch->colorSwatchMenu());
371 }
372
373 destroyDockWidgetMenu = new QMenu(tr(s: "Destroy dock widget"), this);
374 destroyDockWidgetMenu->setEnabled(false);
375 connect(sender: destroyDockWidgetMenu, signal: &QMenu::triggered, receiver: this, slot: &MainWindow::destroyDockWidget);
376
377 dockWidgetMenu->addSeparator();
378 dockWidgetMenu->addAction(text: tr(s: "Add dock widget..."), object: this, slot: &MainWindow::createDockWidget);
379 dockWidgetMenu->addMenu(menu: destroyDockWidgetMenu);
380}
381
382void MainWindow::switchLayoutDirection()
383{
384 if (layoutDirection() == Qt::LeftToRight)
385 QApplication::setLayoutDirection(Qt::RightToLeft);
386 else
387 QApplication::setLayoutDirection(Qt::LeftToRight);
388}
389
390class CreateDockWidgetDialog : public QDialog
391{
392public:
393 explicit CreateDockWidgetDialog(QWidget *parent = nullptr);
394
395 QString enteredObjectName() const { return m_objectName->text(); }
396 Qt::DockWidgetArea location() const;
397
398private:
399 QLineEdit *m_objectName;
400 QComboBox *m_location;
401};
402
403CreateDockWidgetDialog::CreateDockWidgetDialog(QWidget *parent)
404 : QDialog(parent)
405 , m_objectName(new QLineEdit(this))
406 , m_location(new QComboBox(this))
407{
408 setWindowTitle(tr(s: "Add Dock Widget"));
409 setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
410 QGridLayout *layout = new QGridLayout(this);
411
412 layout->addWidget(new QLabel(tr(s: "Object name:")), row: 0, column: 0);
413 layout->addWidget(m_objectName, row: 0, column: 1);
414
415 layout->addWidget(new QLabel(tr(s: "Location:")), row: 1, column: 0);
416 m_location->setEditable(false);
417 m_location->addItem(atext: tr(s: "Top"));
418 m_location->addItem(atext: tr(s: "Left"));
419 m_location->addItem(atext: tr(s: "Right"));
420 m_location->addItem(atext: tr(s: "Bottom"));
421 m_location->addItem(atext: tr(s: "Restore"));
422 layout->addWidget(m_location, row: 1, column: 1);
423
424 QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
425 connect(sender: buttonBox, signal: &QDialogButtonBox::rejected, receiver: this, slot: &QDialog::reject);
426 connect(sender: buttonBox, signal: &QDialogButtonBox::accepted, receiver: this, slot: &QDialog::accept);
427 layout->addWidget(buttonBox, row: 2, column: 0, rowSpan: 1, columnSpan: 2);
428}
429
430Qt::DockWidgetArea CreateDockWidgetDialog::location() const
431{
432 switch (m_location->currentIndex()) {
433 case 0: return Qt::TopDockWidgetArea;
434 case 1: return Qt::LeftDockWidgetArea;
435 case 2: return Qt::RightDockWidgetArea;
436 case 3: return Qt::BottomDockWidgetArea;
437 default:
438 break;
439 }
440 return Qt::NoDockWidgetArea;
441}
442
443void MainWindow::createDockWidget()
444{
445 CreateDockWidgetDialog dialog(this);
446 if (dialog.exec() == QDialog::Rejected)
447 return;
448
449 QDockWidget *dw = new QDockWidget;
450 const QString name = dialog.enteredObjectName();
451 dw->setObjectName(name);
452 dw->setWindowTitle(name);
453 dw->setWidget(new QTextEdit);
454
455 Qt::DockWidgetArea area = dialog.location();
456 switch (area) {
457 case Qt::LeftDockWidgetArea:
458 case Qt::RightDockWidgetArea:
459 case Qt::TopDockWidgetArea:
460 case Qt::BottomDockWidgetArea:
461 addDockWidget(area, dockwidget: dw);
462 break;
463 default:
464 if (!restoreDockWidget(dockwidget: dw)) {
465 QMessageBox::warning(parent: this, title: QString(), text: tr(s: "Failed to restore dock widget"));
466 delete dw;
467 return;
468 }
469 break;
470 }
471
472 extraDockWidgets.append(t: dw);
473 destroyDockWidgetMenu->setEnabled(true);
474 destroyDockWidgetMenu->addAction(action: new QAction(name, this));
475}
476
477void MainWindow::destroyDockWidget(QAction *action)
478{
479 int index = destroyDockWidgetMenu->actions().indexOf(t: action);
480 delete extraDockWidgets.takeAt(i: index);
481 destroyDockWidgetMenu->removeAction(action);
482 action->deleteLater();
483
484 if (destroyDockWidgetMenu->isEmpty())
485 destroyDockWidgetMenu->setEnabled(false);
486}
487
488void MainWindow::about()
489{
490 QMessageBox::about(parent: this, title: tr(s: "About MainWindows"), text: message);
491}
492

source code of qtbase/examples/widgets/mainwindows/mainwindow/mainwindow.cpp