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 examples 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 <QtWidgets>
52
53#include "mainwindow.h"
54#include "mdichild.h"
55
56MainWindow::MainWindow()
57 : mdiArea(new QMdiArea)
58{
59 mdiArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
60 mdiArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
61 setCentralWidget(mdiArea);
62 connect(mdiArea, &QMdiArea::subWindowActivated,
63 this, &MainWindow::updateMenus);
64
65 createActions();
66 createStatusBar();
67 updateMenus();
68
69 readSettings();
70
71 setWindowTitle(tr("MDI"));
72 setUnifiedTitleAndToolBarOnMac(true);
73}
74
75void MainWindow::closeEvent(QCloseEvent *event)
76{
77 mdiArea->closeAllSubWindows();
78 if (mdiArea->currentSubWindow()) {
79 event->ignore();
80 } else {
81 writeSettings();
82 event->accept();
83 }
84}
85
86void MainWindow::newFile()
87{
88 MdiChild *child = createMdiChild();
89 child->newFile();
90 child->show();
91}
92
93void MainWindow::open()
94{
95 const QString fileName = QFileDialog::getOpenFileName(this);
96 if (!fileName.isEmpty())
97 openFile(fileName);
98}
99
100bool MainWindow::openFile(const QString &fileName)
101{
102 if (QMdiSubWindow *existing = findMdiChild(fileName)) {
103 mdiArea->setActiveSubWindow(existing);
104 return true;
105 }
106 const bool succeeded = loadFile(fileName);
107 if (succeeded)
108 statusBar()->showMessage(tr("File loaded"), 2000);
109 return succeeded;
110}
111
112bool MainWindow::loadFile(const QString &fileName)
113{
114 MdiChild *child = createMdiChild();
115 const bool succeeded = child->loadFile(fileName);
116 if (succeeded)
117 child->show();
118 else
119 child->close();
120 MainWindow::prependToRecentFiles(fileName);
121 return succeeded;
122}
123
124static inline QString recentFilesKey() { return QStringLiteral("recentFileList"); }
125static inline QString fileKey() { return QStringLiteral("file"); }
126
127static QStringList readRecentFiles(QSettings &settings)
128{
129 QStringList result;
130 const int count = settings.beginReadArray(recentFilesKey());
131 for (int i = 0; i < count; ++i) {
132 settings.setArrayIndex(i);
133 result.append(settings.value(fileKey()).toString());
134 }
135 settings.endArray();
136 return result;
137}
138
139static void writeRecentFiles(const QStringList &files, QSettings &settings)
140{
141 const int count = files.size();
142 settings.beginWriteArray(recentFilesKey());
143 for (int i = 0; i < count; ++i) {
144 settings.setArrayIndex(i);
145 settings.setValue(fileKey(), files.at(i));
146 }
147 settings.endArray();
148}
149
150bool MainWindow::hasRecentFiles()
151{
152 QSettings settings(QCoreApplication::organizationName(), QCoreApplication::applicationName());
153 const int count = settings.beginReadArray(recentFilesKey());
154 settings.endArray();
155 return count > 0;
156}
157
158void MainWindow::prependToRecentFiles(const QString &fileName)
159{
160 QSettings settings(QCoreApplication::organizationName(), QCoreApplication::applicationName());
161
162 const QStringList oldRecentFiles = readRecentFiles(settings);
163 QStringList recentFiles = oldRecentFiles;
164 recentFiles.removeAll(fileName);
165 recentFiles.prepend(fileName);
166 if (oldRecentFiles != recentFiles)
167 writeRecentFiles(recentFiles, settings);
168
169 setRecentFilesVisible(!recentFiles.isEmpty());
170}
171
172void MainWindow::setRecentFilesVisible(bool visible)
173{
174 recentFileSubMenuAct->setVisible(visible);
175 recentFileSeparator->setVisible(visible);
176}
177
178void MainWindow::updateRecentFileActions()
179{
180 QSettings settings(QCoreApplication::organizationName(), QCoreApplication::applicationName());
181
182 const QStringList recentFiles = readRecentFiles(settings);
183 const int count = qMin(int(MaxRecentFiles), recentFiles.size());
184 int i = 0;
185 for ( ; i < count; ++i) {
186 const QString fileName = QFileInfo(recentFiles.at(i)).fileName();
187 recentFileActs[i]->setText(tr("&%1 %2").arg(i + 1).arg(fileName));
188 recentFileActs[i]->setData(recentFiles.at(i));
189 recentFileActs[i]->setVisible(true);
190 }
191 for ( ; i < MaxRecentFiles; ++i)
192 recentFileActs[i]->setVisible(false);
193}
194
195void MainWindow::openRecentFile()
196{
197 if (const QAction *action = qobject_cast<const QAction *>(sender()))
198 openFile(action->data().toString());
199}
200
201void MainWindow::save()
202{
203 if (activeMdiChild() && activeMdiChild()->save())
204 statusBar()->showMessage(tr("File saved"), 2000);
205}
206
207void MainWindow::saveAs()
208{
209 MdiChild *child = activeMdiChild();
210 if (child && child->saveAs()) {
211 statusBar()->showMessage(tr("File saved"), 2000);
212 MainWindow::prependToRecentFiles(child->currentFile());
213 }
214}
215
216#ifndef QT_NO_CLIPBOARD
217void MainWindow::cut()
218{
219 if (activeMdiChild())
220 activeMdiChild()->cut();
221}
222
223void MainWindow::copy()
224{
225 if (activeMdiChild())
226 activeMdiChild()->copy();
227}
228
229void MainWindow::paste()
230{
231 if (activeMdiChild())
232 activeMdiChild()->paste();
233}
234#endif
235
236void MainWindow::about()
237{
238 QMessageBox::about(this, tr("About MDI"),
239 tr("The <b>MDI</b> example demonstrates how to write multiple "
240 "document interface applications using Qt."));
241}
242
243void MainWindow::updateMenus()
244{
245 bool hasMdiChild = (activeMdiChild() != nullptr);
246 saveAct->setEnabled(hasMdiChild);
247 saveAsAct->setEnabled(hasMdiChild);
248#ifndef QT_NO_CLIPBOARD
249 pasteAct->setEnabled(hasMdiChild);
250#endif
251 closeAct->setEnabled(hasMdiChild);
252 closeAllAct->setEnabled(hasMdiChild);
253 tileAct->setEnabled(hasMdiChild);
254 cascadeAct->setEnabled(hasMdiChild);
255 nextAct->setEnabled(hasMdiChild);
256 previousAct->setEnabled(hasMdiChild);
257 windowMenuSeparatorAct->setVisible(hasMdiChild);
258
259#ifndef QT_NO_CLIPBOARD
260 bool hasSelection = (activeMdiChild() &&
261 activeMdiChild()->textCursor().hasSelection());
262 cutAct->setEnabled(hasSelection);
263 copyAct->setEnabled(hasSelection);
264#endif
265}
266
267void MainWindow::updateWindowMenu()
268{
269 windowMenu->clear();
270 windowMenu->addAction(closeAct);
271 windowMenu->addAction(closeAllAct);
272 windowMenu->addSeparator();
273 windowMenu->addAction(tileAct);
274 windowMenu->addAction(cascadeAct);
275 windowMenu->addSeparator();
276 windowMenu->addAction(nextAct);
277 windowMenu->addAction(previousAct);
278 windowMenu->addAction(windowMenuSeparatorAct);
279
280 QList<QMdiSubWindow *> windows = mdiArea->subWindowList();
281 windowMenuSeparatorAct->setVisible(!windows.isEmpty());
282
283 for (int i = 0; i < windows.size(); ++i) {
284 QMdiSubWindow *mdiSubWindow = windows.at(i);
285 MdiChild *child = qobject_cast<MdiChild *>(mdiSubWindow->widget());
286
287 QString text;
288 if (i < 9) {
289 text = tr("&%1 %2").arg(i + 1)
290 .arg(child->userFriendlyCurrentFile());
291 } else {
292 text = tr("%1 %2").arg(i + 1)
293 .arg(child->userFriendlyCurrentFile());
294 }
295 QAction *action = windowMenu->addAction(text, mdiSubWindow, [this, mdiSubWindow]() {
296 mdiArea->setActiveSubWindow(mdiSubWindow);
297 });
298 action->setCheckable(true);
299 action ->setChecked(child == activeMdiChild());
300 }
301}
302
303MdiChild *MainWindow::createMdiChild()
304{
305 MdiChild *child = new MdiChild;
306 mdiArea->addSubWindow(child);
307
308#ifndef QT_NO_CLIPBOARD
309 connect(child, &QTextEdit::copyAvailable, cutAct, &QAction::setEnabled);
310 connect(child, &QTextEdit::copyAvailable, copyAct, &QAction::setEnabled);
311#endif
312
313 return child;
314}
315
316void MainWindow::createActions()
317{
318 QMenu *fileMenu = menuBar()->addMenu(tr("&File"));
319 QToolBar *fileToolBar = addToolBar(tr("File"));
320
321 const QIcon newIcon = QIcon::fromTheme("document-new", QIcon(":/images/new.png"));
322 newAct = new QAction(newIcon, tr("&New"), this);
323 newAct->setShortcuts(QKeySequence::New);
324 newAct->setStatusTip(tr("Create a new file"));
325 connect(newAct, &QAction::triggered, this, &MainWindow::newFile);
326 fileMenu->addAction(newAct);
327 fileToolBar->addAction(newAct);
328
329 const QIcon openIcon = QIcon::fromTheme("document-open", QIcon(":/images/open.png"));
330 QAction *openAct = new QAction(openIcon, tr("&Open..."), this);
331 openAct->setShortcuts(QKeySequence::Open);
332 openAct->setStatusTip(tr("Open an existing file"));
333 connect(openAct, &QAction::triggered, this, &MainWindow::open);
334 fileMenu->addAction(openAct);
335 fileToolBar->addAction(openAct);
336
337 const QIcon saveIcon = QIcon::fromTheme("document-save", QIcon(":/images/save.png"));
338 saveAct = new QAction(saveIcon, tr("&Save"), this);
339 saveAct->setShortcuts(QKeySequence::Save);
340 saveAct->setStatusTip(tr("Save the document to disk"));
341 connect(saveAct, &QAction::triggered, this, &MainWindow::save);
342 fileToolBar->addAction(saveAct);
343
344 const QIcon saveAsIcon = QIcon::fromTheme("document-save-as");
345 saveAsAct = new QAction(saveAsIcon, tr("Save &As..."), this);
346 saveAsAct->setShortcuts(QKeySequence::SaveAs);
347 saveAsAct->setStatusTip(tr("Save the document under a new name"));
348 connect(saveAsAct, &QAction::triggered, this, &MainWindow::saveAs);
349 fileMenu->addAction(saveAsAct);
350
351 fileMenu->addSeparator();
352
353 QMenu *recentMenu = fileMenu->addMenu(tr("Recent..."));
354 connect(recentMenu, &QMenu::aboutToShow, this, &MainWindow::updateRecentFileActions);
355 recentFileSubMenuAct = recentMenu->menuAction();
356
357 for (int i = 0; i < MaxRecentFiles; ++i) {
358 recentFileActs[i] = recentMenu->addAction(QString(), this, &MainWindow::openRecentFile);
359 recentFileActs[i]->setVisible(false);
360 }
361
362 recentFileSeparator = fileMenu->addSeparator();
363
364 setRecentFilesVisible(MainWindow::hasRecentFiles());
365
366 fileMenu->addAction(tr("Switch layout direction"), this, &MainWindow::switchLayoutDirection);
367
368 fileMenu->addSeparator();
369
370//! [0]
371 const QIcon exitIcon = QIcon::fromTheme("application-exit");
372 QAction *exitAct = fileMenu->addAction(exitIcon, tr("E&xit"), qApp, &QApplication::quit);
373 exitAct->setShortcuts(QKeySequence::Quit);
374 exitAct->setStatusTip(tr("Exit the application"));
375 fileMenu->addAction(exitAct);
376//! [0]
377
378#ifndef QT_NO_CLIPBOARD
379 QMenu *editMenu = menuBar()->addMenu(tr("&Edit"));
380 QToolBar *editToolBar = addToolBar(tr("Edit"));
381
382 const QIcon cutIcon = QIcon::fromTheme("edit-cut", QIcon(":/images/cut.png"));
383 cutAct = new QAction(cutIcon, tr("Cu&t"), this);
384 cutAct->setShortcuts(QKeySequence::Cut);
385 cutAct->setStatusTip(tr("Cut the current selection's contents to the "
386 "clipboard"));
387 connect(cutAct, &QAction::triggered, this, &MainWindow::cut);
388 editMenu->addAction(cutAct);
389 editToolBar->addAction(cutAct);
390
391 const QIcon copyIcon = QIcon::fromTheme("edit-copy", QIcon(":/images/copy.png"));
392 copyAct = new QAction(copyIcon, tr("&Copy"), this);
393 copyAct->setShortcuts(QKeySequence::Copy);
394 copyAct->setStatusTip(tr("Copy the current selection's contents to the "
395 "clipboard"));
396 connect(copyAct, &QAction::triggered, this, &MainWindow::copy);
397 editMenu->addAction(copyAct);
398 editToolBar->addAction(copyAct);
399
400 const QIcon pasteIcon = QIcon::fromTheme("edit-paste", QIcon(":/images/paste.png"));
401 pasteAct = new QAction(pasteIcon, tr("&Paste"), this);
402 pasteAct->setShortcuts(QKeySequence::Paste);
403 pasteAct->setStatusTip(tr("Paste the clipboard's contents into the current "
404 "selection"));
405 connect(pasteAct, &QAction::triggered, this, &MainWindow::paste);
406 editMenu->addAction(pasteAct);
407 editToolBar->addAction(pasteAct);
408#endif
409
410 windowMenu = menuBar()->addMenu(tr("&Window"));
411 connect(windowMenu, &QMenu::aboutToShow, this, &MainWindow::updateWindowMenu);
412
413 closeAct = new QAction(tr("Cl&ose"), this);
414 closeAct->setStatusTip(tr("Close the active window"));
415 connect(closeAct, &QAction::triggered,
416 mdiArea, &QMdiArea::closeActiveSubWindow);
417
418 closeAllAct = new QAction(tr("Close &All"), this);
419 closeAllAct->setStatusTip(tr("Close all the windows"));
420 connect(closeAllAct, &QAction::triggered, mdiArea, &QMdiArea::closeAllSubWindows);
421
422 tileAct = new QAction(tr("&Tile"), this);
423 tileAct->setStatusTip(tr("Tile the windows"));
424 connect(tileAct, &QAction::triggered, mdiArea, &QMdiArea::tileSubWindows);
425
426 cascadeAct = new QAction(tr("&Cascade"), this);
427 cascadeAct->setStatusTip(tr("Cascade the windows"));
428 connect(cascadeAct, &QAction::triggered, mdiArea, &QMdiArea::cascadeSubWindows);
429
430 nextAct = new QAction(tr("Ne&xt"), this);
431 nextAct->setShortcuts(QKeySequence::NextChild);
432 nextAct->setStatusTip(tr("Move the focus to the next window"));
433 connect(nextAct, &QAction::triggered, mdiArea, &QMdiArea::activateNextSubWindow);
434
435 previousAct = new QAction(tr("Pre&vious"), this);
436 previousAct->setShortcuts(QKeySequence::PreviousChild);
437 previousAct->setStatusTip(tr("Move the focus to the previous "
438 "window"));
439 connect(previousAct, &QAction::triggered, mdiArea, &QMdiArea::activatePreviousSubWindow);
440
441 windowMenuSeparatorAct = new QAction(this);
442 windowMenuSeparatorAct->setSeparator(true);
443
444 updateWindowMenu();
445
446 menuBar()->addSeparator();
447
448 QMenu *helpMenu = menuBar()->addMenu(tr("&Help"));
449
450 QAction *aboutAct = helpMenu->addAction(tr("&About"), this, &MainWindow::about);
451 aboutAct->setStatusTip(tr("Show the application's About box"));
452
453 QAction *aboutQtAct = helpMenu->addAction(tr("About &Qt"), qApp, &QApplication::aboutQt);
454 aboutQtAct->setStatusTip(tr("Show the Qt library's About box"));
455}
456
457void MainWindow::createStatusBar()
458{
459 statusBar()->showMessage(tr("Ready"));
460}
461
462void MainWindow::readSettings()
463{
464 QSettings settings(QCoreApplication::organizationName(), QCoreApplication::applicationName());
465 const QByteArray geometry = settings.value("geometry", QByteArray()).toByteArray();
466 if (geometry.isEmpty()) {
467 const QRect availableGeometry = screen()->availableGeometry();
468 resize(availableGeometry.width() / 3, availableGeometry.height() / 2);
469 move((availableGeometry.width() - width()) / 2,
470 (availableGeometry.height() - height()) / 2);
471 } else {
472 restoreGeometry(geometry);
473 }
474}
475
476void MainWindow::writeSettings()
477{
478 QSettings settings(QCoreApplication::organizationName(), QCoreApplication::applicationName());
479 settings.setValue("geometry", saveGeometry());
480}
481
482MdiChild *MainWindow::activeMdiChild() const
483{
484 if (QMdiSubWindow *activeSubWindow = mdiArea->activeSubWindow())
485 return qobject_cast<MdiChild *>(activeSubWindow->widget());
486 return nullptr;
487}
488
489QMdiSubWindow *MainWindow::findMdiChild(const QString &fileName) const
490{
491 QString canonicalFilePath = QFileInfo(fileName).canonicalFilePath();
492
493 const QList<QMdiSubWindow *> subWindows = mdiArea->subWindowList();
494 for (QMdiSubWindow *window : subWindows) {
495 MdiChild *mdiChild = qobject_cast<MdiChild *>(window->widget());
496 if (mdiChild->currentFile() == canonicalFilePath)
497 return window;
498 }
499 return nullptr;
500}
501
502void MainWindow::switchLayoutDirection()
503{
504 if (layoutDirection() == Qt::LeftToRight)
505 QGuiApplication::setLayoutDirection(Qt::RightToLeft);
506 else
507 QGuiApplication::setLayoutDirection(Qt::LeftToRight);
508}
509