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 <QActionGroup>
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(400, 205);
107 setCentralWidget(center);
108
109 setupToolBar();
110 setupMenuBar();
111 setupDockWidgets(customSizeHints);
112
113 statusBar()->showMessage(tr("Status Bar"));
114}
115
116void MainWindow::actionTriggered(QAction *action)
117{
118 qDebug("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("Tool Bar %1").arg(i + 1), this);
129 toolBars.append(tb);
130 addToolBar(tb);
131 }
132}
133
134void MainWindow::setupMenuBar()
135{
136 QMenu *menu = menuBar()->addMenu(tr("&File"));
137
138 menu->addAction(tr("Save layout..."), this, &MainWindow::saveLayout);
139 menu->addAction(tr("Load layout..."), this, &MainWindow::loadLayout);
140 menu->addAction(tr("Switch layout direction"),this, &MainWindow::switchLayoutDirection);
141
142 menu->addSeparator();
143 menu->addAction(tr("&Quit"), this, &QWidget::close);
144
145 mainWindowMenu = menuBar()->addMenu(tr("Main window"));
146
147 QAction *action = mainWindowMenu->addAction(tr("Animated docks"));
148 action->setCheckable(true);
149 action->setChecked(dockOptions() & AnimatedDocks);
150 connect(action, &QAction::toggled, this, &MainWindow::setDockOptions);
151
152 action = mainWindowMenu->addAction(tr("Allow nested docks"));
153 action->setCheckable(true);
154 action->setChecked(dockOptions() & AllowNestedDocks);
155 connect(action, &QAction::toggled, this, &MainWindow::setDockOptions);
156
157 action = mainWindowMenu->addAction(tr("Allow tabbed docks"));
158 action->setCheckable(true);
159 action->setChecked(dockOptions() & AllowTabbedDocks);
160 connect(action, &QAction::toggled, this, &MainWindow::setDockOptions);
161
162 action = mainWindowMenu->addAction(tr("Force tabbed docks"));
163 action->setCheckable(true);
164 action->setChecked(dockOptions() & ForceTabbedDocks);
165 connect(action, &QAction::toggled, this, &MainWindow::setDockOptions);
166
167 action = mainWindowMenu->addAction(tr("Vertical tabs"));
168 action->setCheckable(true);
169 action->setChecked(dockOptions() & VerticalTabs);
170 connect(action, &QAction::toggled, this, &MainWindow::setDockOptions);
171
172 action = mainWindowMenu->addAction(tr("Grouped dragging"));
173 action->setCheckable(true);
174 action->setChecked(dockOptions() & GroupedDragging);
175 connect(action, &QAction::toggled, this, &MainWindow::setDockOptions);
176
177 QMenu *toolBarMenu = menuBar()->addMenu(tr("Tool bars"));
178 for (int i = 0; i < toolBars.count(); ++i)
179 toolBarMenu->addMenu(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(tr("&Dock Widgets"));
191
192 QMenu *aboutMenu = menuBar()->addMenu(tr("About"));
193 QAction *aboutAct = aboutMenu->addAction(tr("&About"), this, &MainWindow::about);
194 aboutAct->setStatusTip(tr("Show the application's About box"));
195
196 QAction *aboutQtAct = aboutMenu->addAction(tr("About &Qt"), qApp, &QApplication::aboutQt);
197 aboutQtAct->setStatusTip(tr("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(0)->isChecked())
206 opts |= AnimatedDocks;
207 if (actions.at(1)->isChecked())
208 opts |= AllowNestedDocks;
209 if (actions.at(2)->isChecked())
210 opts |= AllowTabbedDocks;
211 if (actions.at(3)->isChecked())
212 opts |= ForceTabbedDocks;
213 if (actions.at(4)->isChecked())
214 opts |= VerticalTabs;
215 if (actions.at(5)->isChecked())
216 opts |= GroupedDragging;
217
218 QMainWindow::setDockOptions(opts);
219}
220
221void MainWindow::saveLayout()
222{
223 QString fileName
224 = QFileDialog::getSaveFileName(this, tr("Save layout"));
225 if (fileName.isEmpty())
226 return;
227 QFile file(fileName);
228 if (!file.open(QFile::WriteOnly)) {
229 QString msg = tr("Failed to open %1\n%2")
230 .arg(QDir::toNativeSeparators(fileName), file.errorString());
231 QMessageBox::warning(this, tr("Error"), msg);
232 return;
233 }
234
235 QByteArray geo_data = saveGeometry();
236 QByteArray layout_data = saveState();
237
238 bool ok = file.putChar((uchar)geo_data.size());
239 if (ok)
240 ok = file.write(geo_data) == geo_data.size();
241 if (ok)
242 ok = file.write(layout_data) == layout_data.size();
243
244 if (!ok) {
245 QString msg = tr("Error writing to %1\n%2")
246 .arg(QDir::toNativeSeparators(fileName), file.errorString());
247 QMessageBox::warning(this, tr("Error"), msg);
248 return;
249 }
250}
251
252void MainWindow::loadLayout()
253{
254 QString fileName
255 = QFileDialog::getOpenFileName(this, tr("Load layout"));
256 if (fileName.isEmpty())
257 return;
258 QFile file(fileName);
259 if (!file.open(QFile::ReadOnly)) {
260 QString msg = tr("Failed to open %1\n%2")
261 .arg(QDir::toNativeSeparators(fileName), file.errorString());
262 QMessageBox::warning(this, tr("Error"), msg);
263 return;
264 }
265
266 uchar geo_size;
267 QByteArray geo_data;
268 QByteArray layout_data;
269
270 bool ok = file.getChar((char*)&geo_size);
271 if (ok) {
272 geo_data = file.read(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(geo_data);
282 if (ok)
283 ok = restoreState(layout_data);
284
285 if (!ok) {
286 QString msg = tr("Error reading %1").arg(QDir::toNativeSeparators(fileName));
287 QMessageBox::warning(this, tr("Error"), 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, mw, [=]() { mw->setCorner(c, a); });
296 result->setCheckable(true);
297 group->addAction(result);
298 return result;
299}
300
301void MainWindow::setupDockWidgets(const CustomSizeHintMap &customSizeHints)
302{
303 qRegisterMetaType<QDockWidget::DockWidgetFeatures>();
304
305 QMenu *cornerMenu = dockWidgetMenu->addMenu(tr("Top left corner"));
306 QActionGroup *group = new QActionGroup(this);
307 group->setExclusive(true);
308 QAction *cornerAction = addCornerAction(tr("Top dock area"), this, cornerMenu, group, Qt::TopLeftCorner, Qt::TopDockWidgetArea);
309 cornerAction->setChecked(true);
310 addCornerAction(tr("Left dock area"), this, cornerMenu, group, Qt::TopLeftCorner, Qt::LeftDockWidgetArea);
311
312 cornerMenu = dockWidgetMenu->addMenu(tr("Top right corner"));
313 group = new QActionGroup(this);
314 group->setExclusive(true);
315 cornerAction = addCornerAction(tr("Top dock area"), this, cornerMenu, group, Qt::TopRightCorner, Qt::TopDockWidgetArea);
316 cornerAction->setChecked(true);
317 addCornerAction(tr("Right dock area"), this, cornerMenu, group, Qt::TopRightCorner, Qt::RightDockWidgetArea);
318
319 cornerMenu = dockWidgetMenu->addMenu(tr("Bottom left corner"));
320 group = new QActionGroup(this);
321 group->setExclusive(true);
322 cornerAction = addCornerAction(tr("Bottom dock area"), this, cornerMenu, group, Qt::BottomLeftCorner, Qt::BottomDockWidgetArea);
323 cornerAction->setChecked(true);
324 addCornerAction(tr("Left dock area"), this, cornerMenu, group, Qt::BottomLeftCorner, Qt::LeftDockWidgetArea);
325
326 cornerMenu = dockWidgetMenu->addMenu(tr("Bottom right corner"));
327 group = new QActionGroup(this);
328 group->setExclusive(true);
329 cornerAction = addCornerAction(tr("Bottom dock area"), this, cornerMenu, group, Qt::BottomRightCorner, Qt::BottomDockWidgetArea);
330 cornerAction->setChecked(true);
331 addCornerAction(tr("Right dock area"), this, cornerMenu, group, Qt::BottomRightCorner, 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 { "Black", 0, Qt::LeftDockWidgetArea },
342#else
343 { "Black", Qt::Drawer, Qt::LeftDockWidgetArea },
344#endif
345 { "White", 0, Qt::RightDockWidgetArea },
346 { "Red", 0, Qt::TopDockWidgetArea },
347 { "Green", 0, Qt::TopDockWidgetArea },
348 { "Blue", 0, Qt::BottomDockWidgetArea },
349 { "Yellow", 0, 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(sets[i].name), this, Qt::WindowFlags(sets[i].flags));
356 if (i % 2)
357 swatch->setWindowIcon(qtIcon);
358 if (qstrcmp(sets[i].name, "Blue") == 0) {
359 BlueTitleBar *titlebar = new BlueTitleBar(swatch);
360 swatch->setTitleBarWidget(titlebar);
361 connect(swatch, &QDockWidget::topLevelChanged, titlebar, &BlueTitleBar::updateMask);
362 connect(swatch, &QDockWidget::featuresChanged, titlebar, &BlueTitleBar::updateMask, Qt::QueuedConnection);
363 }
364
365 QString name = QString::fromLatin1(sets[i].name);
366 if (customSizeHints.contains(name))
367 swatch->setCustomSizeHint(customSizeHints.value(name));
368
369 addDockWidget(sets[i].area, swatch);
370 dockWidgetMenu->addMenu(swatch->colorSwatchMenu());
371 }
372
373 destroyDockWidgetMenu = new QMenu(tr("Destroy dock widget"), this);
374 destroyDockWidgetMenu->setEnabled(false);
375 connect(destroyDockWidgetMenu, &QMenu::triggered, this, &MainWindow::destroyDockWidget);
376
377 dockWidgetMenu->addSeparator();
378 dockWidgetMenu->addAction(tr("Add dock widget..."), this, &MainWindow::createDockWidget);
379 dockWidgetMenu->addMenu(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("Add Dock Widget"));
409 setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
410 QGridLayout *layout = new QGridLayout(this);
411
412 layout->addWidget(new QLabel(tr("Object name:")), 0, 0);
413 layout->addWidget(m_objectName, 0, 1);
414
415 layout->addWidget(new QLabel(tr("Location:")), 1, 0);
416 m_location->setEditable(false);
417 m_location->addItem(tr("Top"));
418 m_location->addItem(tr("Left"));
419 m_location->addItem(tr("Right"));
420 m_location->addItem(tr("Bottom"));
421 m_location->addItem(tr("Restore"));
422 layout->addWidget(m_location, 1, 1);
423
424 QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
425 connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
426 connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
427 layout->addWidget(buttonBox, 2, 0, 1, 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, dw);
462 break;
463 default:
464 if (!restoreDockWidget(dw)) {
465 QMessageBox::warning(this, QString(), tr("Failed to restore dock widget"));
466 delete dw;
467 return;
468 }
469 break;
470 }
471
472 extraDockWidgets.append(dw);
473 destroyDockWidgetMenu->setEnabled(true);
474 destroyDockWidgetMenu->addAction(new QAction(name, this));
475}
476
477void MainWindow::destroyDockWidget(QAction *action)
478{
479 int index = destroyDockWidgetMenu->actions().indexOf(action);
480 delete extraDockWidgets.takeAt(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(this, tr("About MainWindows"), message);
491}
492