Currently job artifacts in CI/CD pipelines on LRZ GitLab never expire. Starting from Wed 26.1.2022 the default expiration time will be 30 days (GitLab default). Currently existing artifacts in already completed jobs will not be affected by the change. The latest artifacts for all jobs in the latest successful pipelines will be kept. More information: https://gitlab.lrz.de/help/user/admin_area/settings/continuous_integration.html#default-artifacts-expiration

Commit 0c73f0f7 authored by Christian Schulte zu Berge's avatar Christian Schulte zu Berge
Browse files

Merge branch 'main-menu' of /mnt/bigone/git/repositories/berge/campvis into development

parents 7c9beba2 6d4262de
......@@ -38,6 +38,7 @@ SET(CampvisApplicationToBeMocced
gui/logviewerwidget.h
gui/loghighlighter.h
gui/mdi/mdidockarea.h
gui/mdi/mdidockablewindow.h
gui/mdi/mdidockedwindow.h
gui/mdi/mdifloatingwindow.h
gui/adjusterwidgets/doubleadjusterwidget.h
......
......@@ -33,6 +33,7 @@
#include "application/campvisapplication.h"
#include "application/gui/datacontainerinspectorwidget.h"
#include "application/gui/datacontainerinspectorcanvas.h"
#include "application/gui/mdi/mdidockablewindow.h"
#include "application/gui/qtdatahandle.h"
#include "core/datastructures/datacontainer.h"
#include "core/pipeline/abstractpipeline.h"
......@@ -40,7 +41,6 @@
#include "core/tools/stringutils.h"
#include "modules/pipelinefactory.h"
#include <QMdiSubWindow>
#include <QScrollBar>
......@@ -136,6 +136,7 @@ namespace campvis {
ui.logViewerDock->setWidget(_logViewer);
_dcInspectorWidget = new DataContainerInspectorWidget();
this->populateMainMenu();
connect(
this, SIGNAL(updatePipelineWidget(const std::vector<DataContainer*>&, const std::vector<AbstractPipeline*>&)),
......@@ -160,6 +161,24 @@ namespace campvis {
_application->s_DataContainersChanged.connect(this, &MainWindow::onDataContainersChanged);
}
void MainWindow::populateMainMenu() {
// Populate the file menu
QMenuBar* menuBar = this->menuBar();
QMenu* fileMenu = menuBar->addMenu(tr("&File"));
fileMenu->addAction(tr("&Quit"), qApp, SLOT(closeAllWindows()), QKeySequence(Qt::CTRL + Qt::Key_Q));
// Populate the visualizations menu
QMenu* visualizationsMenu = _mdiArea->menu();
visualizationsMenu->setTitle(tr("&Visualizations"));
menuBar->addMenu(visualizationsMenu);
// Populate the tools menu
QMenu* toolsMenu = menuBar->addMenu(tr("&Tools"));
toolsMenu->addAction(ui.pipelineTreeDock->toggleViewAction());
toolsMenu->addAction(ui.pipelinePropertiesDock->toggleViewAction());
toolsMenu->addAction(ui.logViewerDock->toggleViewAction());
}
bool MainWindow::eventFilter(QObject* watched, QEvent* event) {
if (watched == _pipelinePropertiesWidget && event->type() == QEvent::Resize) {
_pipelinePropertiesScrollArea->setMinimumWidth(_pipelinePropertiesWidget->minimumSizeHint().width() +
......@@ -259,9 +278,10 @@ namespace campvis {
}
void MainWindow::addVisualizationPipelineWidget(const std::string& name, QWidget* canvas) {
QMdiSubWindow* mdiSubWindow = _mdiArea->addSubWindow(canvas);
MdiDockableWindow* dockableWindow = _mdiArea->addWidget(canvas);
const QString& windowTitle = QString::fromStdString(name);
mdiSubWindow->setWindowTitle(windowTitle);
dockableWindow->setWindowTitle(windowTitle);
dockableWindow->show();
}
QDockWidget* MainWindow::dockPrimaryWidget(const std::string& name, QWidget* widget) {
......
......@@ -141,12 +141,16 @@ namespace campvis {
*/
void onDataContainersChanged();
/**
* Setup Qt GUI stuff
*/
void setup();
/**
* Create and populate the application's main menu.
*/
void populateMainMenu();
/**
* Adds a widget to the top docking area of the main window.
* This method creates a new dock with the specified name,
......
// ================================================================================================
//
// This file is part of the CAMPVis Software Framework.
//
// If not explicitly stated otherwise: Copyright (C) 2012, all rights reserved,
// Christian Schulte zu Berge <christian.szb@in.tum.de>
// Chair for Computer Aided Medical Procedures
// Technische Universität München
// Boltzmannstr. 3, 85748 Garching b. München, Germany
// For a full list of authors and contributors, please refer to the file "AUTHORS.txt".
//
// The licensing of this softare is not yet resolved. Until then, redistribution in source or
// binary forms outside the CAMP chair is not permitted, unless explicitly stated in legal form.
// However, the names of the original authors and the above copyright notice must retain in its
// original state in any case.
//
// Legal disclaimer provided by the BSD license:
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
// ================================================================================================
#include "mdidockablewindow.h"
#include "mdidockarea.h"
#include <QAction>
namespace campvis {
MdiDockableWindow::MdiDockableWindow(QWidget* widget, MdiDockArea* mdiArea, Qt::WindowFlags windowFlags /*= 0*/)
: QWidget(mdiArea)
, _docked(true)
, _mdiArea(mdiArea)
, _dockedWindow(0)
, _floatingWindow(0)
, _toggleViewAction(0)
{
_dockedWindow = this->newDockedWindow(widget);
_dockedWindow->setWindowFlags(windowFlags);
_toggleViewAction = new QAction(this);
_toggleViewAction->setCheckable(true);
_toggleViewAction->setChecked(false);
this->connect(_toggleViewAction, SIGNAL(toggled(bool)), SLOT(toggleWindowVisibility(bool)));
}
void MdiDockableWindow::setWindowTitle(const QString& title) {
QWidget::setWindowTitle(title);
_toggleViewAction->setText(title);
if (_docked)
_dockedWindow->setWindowTitle(title);
else
_floatingWindow->setWindowTitle(title);
}
QAction* MdiDockableWindow::toggleViewAction() const {
return _toggleViewAction;
}
void MdiDockableWindow::setVisible(bool visible) {
_toggleViewAction->setChecked(visible);
}
MdiDockedWindow* MdiDockableWindow::newDockedWindow(QWidget* widget) {
MdiDockedWindow* dockedWindow = new MdiDockedWindow(_mdiArea);
dockedWindow->setWidget(widget);
this->connect(dockedWindow, SIGNAL(s_positionChanged(const QPoint&)), SLOT(trackDockedWindowPosition(QPoint)));
this->connect(dockedWindow, SIGNAL(s_closed()), SLOT(handleWindowClosing()));
return dockedWindow;
}
void MdiDockableWindow::toggleWindowVisibility(bool on) {
if (on) {
if (_docked)
_mdiArea->addSubWindow(_dockedWindow);
else
_floatingWindow->show();
}
else {
if (_docked)
_mdiArea->removeSubWindow(_dockedWindow);
else
_floatingWindow->hide();
}
}
void MdiDockableWindow::handleWindowClosing() {
_toggleViewAction->setChecked(false);
}
void MdiDockableWindow::trackFloatingWindowPosition(const QPoint& /*newPos*/) {
const QRect& widgetGeometry = _floatingWindow->frameGeometry();
const QRect& mdiAreaRect = _mdiArea->contentsRect();
const QRect mdiAreaGeometry(_mdiArea->mapToGlobal(mdiAreaRect.topLeft()),
_mdiArea->mapToGlobal(mdiAreaRect.bottomRight()));
const QRect& intersection = widgetGeometry & mdiAreaGeometry;
// Dock the floating window's widget if at least 60% of it is over the MDI area
if (widgetGeometry.width() * widgetGeometry.height() * 3 <
intersection.width() * intersection.height() * 5) {
_floatingWindow->stopWindowDrag();
_floatingWindow->hide();
QWidget* widget = _floatingWindow->widget();
_dockedWindow = this->newDockedWindow(widget);
_dockedWindow->setWindowTitle(this->windowTitle());
_floatingWindow->deleteLater();
_floatingWindow = 0;
_docked = true;
_mdiArea->addSubWindow(_dockedWindow);
// Dragging the window doesn't make sense in tabbed mode
if (_mdiArea->viewMode() == QMdiArea::SubWindowView)
_dockedWindow->forceWindowDrag();
}
}
void MdiDockableWindow::trackDockedWindowPosition(const QPoint& /*newPos*/) {
const QRect& subWindowGeometry = _dockedWindow->frameGeometry();
const QRect& mdiAreaGeometry = _mdiArea->contentsRect();
const QRect& intersection = subWindowGeometry & mdiAreaGeometry;
// Detach the docked window's widget if at least 60% of it has left the MDI area
if (subWindowGeometry.width() * subWindowGeometry.height() * 2 >
intersection.width() * intersection.height() * 5) {
QWidget* widget = _dockedWindow->widget();
_dockedWindow->stopWindowDrag();
_dockedWindow->setWidget(0);
_mdiArea->removeSubWindow(_dockedWindow);
_floatingWindow = new MdiFloatingWindow(widget);
_floatingWindow->setWindowTitle(this->windowTitle());
_floatingWindow->forceWindowDrag();
this->connect(_floatingWindow, SIGNAL(s_positionChanged(const QPoint&)),
SLOT(trackFloatingWindowPosition(const QPoint&)));
this->connect(_floatingWindow, SIGNAL(s_closed()), SLOT(handleWindowClosing()));
_dockedWindow->deleteLater();
_dockedWindow = 0;
_docked = false;
_floatingWindow->show();
}
}
}
// ================================================================================================
//
// This file is part of the CAMPVis Software Framework.
//
// If not explicitly stated otherwise: Copyright (C) 2012, all rights reserved,
// Christian Schulte zu Berge <christian.szb@in.tum.de>
// Chair for Computer Aided Medical Procedures
// Technische Universität München
// Boltzmannstr. 3, 85748 Garching b. München, Germany
// For a full list of authors and contributors, please refer to the file "AUTHORS.txt".
//
// The licensing of this softare is not yet resolved. Until then, redistribution in source or
// binary forms outside the CAMP chair is not permitted, unless explicitly stated in legal form.
// However, the names of the original authors and the above copyright notice must retain in its
// original state in any case.
//
// Legal disclaimer provided by the BSD license:
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
// ================================================================================================
#ifndef MDIDOCKABLEWINDOW_H__
#define MDIDOCKABLEWINDOW_H__
#include "mdidockedwindow.h"
#include "mdifloatingwindow.h"
#include <QWidget>
namespace campvis {
class MdiDockArea;
/**
* MDI window that be docked and undocked.
*
* MdiDockableWindow takes care of creating all necessary representations (docked and floating
* window) of the widget it's passed and seamlessly switching between them in response to the
* user's actions (window dragging, key presses, etc).
*/
class MdiDockableWindow : public QWidget {
Q_OBJECT
public:
/**
* Construct a new MdiDockableWindow.
*
* \param widget the widget this window is to wrap
* \param mdiArea the MDI area the window should dock in
* \param windowFlags flags used to customize the frame of the window
*/
MdiDockableWindow(QWidget* widget, MdiDockArea* mdiArea, Qt::WindowFlags windowFlags = 0);
/**
* Set the title of this window.
*
* \param title the window's new title
*/
void setWindowTitle(const QString& title);
/**
* Change the window's visibility.
*
* \param visible should the window be visible?
*/
virtual void setVisible(bool visible);
/**
* Returns a checkable action that can be used to show or hide this window.
*
* The action's text is set to this window's title.
*/
QAction* toggleViewAction() const;
private slots:
/**
* Track the position of the associated floating MDI window and dock it if necessary.
*
* This slot is invoked when the position of the floating MDI window changes.
*/
void trackFloatingWindowPosition(const QPoint& newPos);
/**
* Track the position of the associated docked MDI window and detach it if necessary.
*
* This slot is invoked when the position of the docked MDI window changes.
*/
void trackDockedWindowPosition(const QPoint& newPos);
/**
* Depending on the state of _toggleViewAction, show or hide the window.
*
* \param on true if _toggleViewAction is checked, false otherwise
*/
void toggleWindowVisibility(bool on);
/**
* Hide the closed MdiDockedWindow/MdiFloatingWindow and update the visibility action.
*/
void handleWindowClosing();
private:
/**
* Create and return an MdiDockedWindow wrapping the \p widget.
*
* \param widget the widget the new docked window should wrap
*/
MdiDockedWindow* newDockedWindow(QWidget* widget);
bool _docked; ///< Is the window currently docked?
MdiDockArea* _mdiArea; ///< The MDI this window docks in.
MdiDockedWindow* _dockedWindow; ///< The window's docked representation.
MdiFloatingWindow* _floatingWindow; ///< The window's floating representation.
QAction* _toggleViewAction; ///< A checkable action that can be used to show or hide this window.
};
}
#endif // MDIDOCKABLEWINDOW_H__
......@@ -5,8 +5,8 @@
// If not explicitly stated otherwise: Copyright (C) 2012, all rights reserved,
// Christian Schulte zu Berge <christian.szb@in.tum.de>
// Chair for Computer Aided Medical Procedures
// Technische Universitt Mnchen
// Boltzmannstr. 3, 85748 Garching b. Mnchen, Germany
// Technische Universität München
// Boltzmannstr. 3, 85748 Garching b. München, Germany
// For a full list of authors and contributors, please refer to the file "AUTHORS.txt".
//
// The licensing of this softare is not yet resolved. Until then, redistribution in source or
......@@ -29,67 +29,76 @@
#include "mdidockarea.h"
#include <QMenu>
namespace campvis {
MdiDockedWindow* MdiDockArea::addSubWindow(QWidget* widget, Qt::WindowFlags windowFlags /*= 0*/) {
MdiDockedWindow* dockedWindow = new MdiDockedWindow();
MdiDockArea::MdiDockArea(QWidget* parent /*= 0*/)
: QMdiArea(parent)
, _menu(0)
{
this->setTabsClosable(true);
this->setTabsMovable(true);
this->setDocumentMode(true);
dockedWindow->setWidget(widget);
QMdiArea::addSubWindow(dockedWindow, windowFlags);
widget->show();
this->tileSubWindows();
// Menu setup
_menu = new QMenu(this);
QActionGroup* displayStyleActions = new QActionGroup(this);
QAction* displayTiledAction = displayStyleActions->addAction(tr("Display tiled"));
displayTiledAction->setCheckable(true);
displayTiledAction->setChecked(true);
_menu->addAction(displayTiledAction);
this->connect(displayTiledAction, SIGNAL(triggered()), SLOT(switchToTiledDisplay()));
connect(dockedWindow, SIGNAL(s_positionChanged(MdiDockedWindow*, const QPoint&)),
this, SLOT(trackMdiSubWindowsPosition(MdiDockedWindow*, const QPoint&)));
QAction* displayTabbedAction = displayStyleActions->addAction(tr("Display tabbed"));
displayTabbedAction->setCheckable(true);
_menu->addAction(displayTabbedAction);
this->connect(displayTabbedAction, SIGNAL(triggered()), SLOT(switchToTabbedDisplay()));
return dockedWindow;
_menu->addSeparator();
}
void MdiDockArea::trackFloatingWindowsPosition(MdiFloatingWindow* floatingWindow, const QPoint& newPos) {
const QRect& widgetGeometry = floatingWindow->frameGeometry();
const QRect& mdiAreaRect = this->contentsRect();
const QRect mdiAreaGeometry(this->mapToGlobal(mdiAreaRect.topLeft()),
this->mapToGlobal(mdiAreaRect.bottomRight()));
const QRect& intersection = widgetGeometry & mdiAreaGeometry;
// Dock the floating window's widget if at least 60% of it is over the MDI area
if (widgetGeometry.width() * widgetGeometry.height() * 3 <
intersection.width() * intersection.height() * 5) {
floatingWindow->stopWindowDrag();
QWidget* widget = floatingWindow->widget();
MdiDockedWindow* dockedWindow = this->addSubWindow(widget);
dockedWindow->setWindowTitle(floatingWindow->windowTitle());
widget->show();
floatingWindow->deleteLater();
dockedWindow->forceWindowDrag();
}
MdiDockableWindow* MdiDockArea::addWidget(QWidget* widget, Qt::WindowFlags windowFlags /*= 0*/) {
MdiDockableWindow* dockableWindow = new MdiDockableWindow(widget, this, windowFlags);
_menu->addAction(dockableWindow->toggleViewAction());
return dockableWindow;
}
QMdiSubWindow* MdiDockArea::addSubWindow(QMdiSubWindow* mdiSubWindow) {
QMdiArea::addSubWindow(mdiSubWindow);
mdiSubWindow->show();
// Calling tileSubWindows() in TabbedView mode breaks the tabbed display
if (this->viewMode() == QMdiArea::SubWindowView)
this->tileSubWindows();
return mdiSubWindow;
}
void MdiDockArea::removeSubWindow(QMdiSubWindow* mdiSubWindow) {
if (this->activeSubWindow() == mdiSubWindow)
this->activateNextSubWindow();
QMdiArea::removeSubWindow(mdiSubWindow);
// Calling tileSubWindows() in TabbedView mode breaks the tabbed display
if (this->viewMode() == QMdiArea::SubWindowView)
this->tileSubWindows();
}
QMenu* MdiDockArea::menu() const {
return _menu;
}
void MdiDockArea::switchToTiledDisplay() {
this->setViewMode(QMdiArea::SubWindowView);
this->tileSubWindows();
}
void MdiDockArea::trackMdiSubWindowsPosition(MdiDockedWindow *dockedWindow, const QPoint& /*newPos*/) {
const QRect& subWindowGeometry = dockedWindow->frameGeometry();
const QRect& mdiAreaGeometry = contentsRect();
const QRect& intersection = subWindowGeometry & mdiAreaGeometry;
// Detach the docked window's widget if at least 60% of it has left the MDI area
if (subWindowGeometry.width() * subWindowGeometry.height() * 2 >
intersection.width() * intersection.height() * 5) {
QWidget* widget = dockedWindow->widget();
dockedWindow->stopWindowDrag();
dockedWindow->setWidget(0);
removeSubWindow(dockedWindow);
dockedWindow->deleteLater();
tileSubWindows();
MdiFloatingWindow* floatingWindow = new MdiFloatingWindow(widget);
floatingWindow->setWindowTitle(dockedWindow->windowTitle());
floatingWindow->show();
floatingWindow->forceWindowDrag();
connect(floatingWindow, SIGNAL(s_positionChanged(MdiFloatingWindow*, const QPoint&)),
this, SLOT(trackFloatingWindowsPosition(MdiFloatingWindow*, const QPoint&)));
}
void MdiDockArea::switchToTabbedDisplay() {
this->setViewMode(QMdiArea::TabbedView);
}
}
......@@ -30,8 +30,7 @@
#ifndef MDIDOCKAREA_H__
#define MDIDOCKAREA_H__
#include "mdidockedwindow.h"
#include "mdifloatingwindow.h"
#include "mdidockablewindow.h"
#include <QMdiArea>
......@@ -40,9 +39,12 @@ namespace campvis {
/**
* MDI area whose subwindows can be docked and undocked.
*
* MdiDockArea takes care of creating all necessary representations (docked and floating window)
* of the widgets passed to \ref addSubWindow and seamlessly switching between them in response
* to the user's actions (window dragging, key presses, etc).
* MdiDockArea extends QMdiArea's functionality by adding support for dockable MDI windows. It
* provides 2 APIs:
* - addSubWindow and removeSubWindow are much like their QMdiArea's counterparts; they operate
* on MDI sub-windows only, which don't support docking/undocking
* - dockable windows can be created using the addWidget method, which returns an
* MdiDockableWindow instance
*/
class MdiDockArea : public QMdiArea {
......@@ -50,30 +52,59 @@ namespace campvis {
public:
/**
* Wrap \p widget in an MDI window and dock it in the MDI area.
* Constructs an empty MDI area.
*
* This method creates a MdiDockedWindow wrapping the widget, and adds it to the MDI area.
* \param parent the area's parent widget (passed to QMdiArea's constructor)
*/
explicit MdiDockArea(QWidget* parent = 0);
/**
* Wrap \p widget in a dockable MDI window and add it to the MDI area.
*
* This method creates a MdiDockableWindow wrapping the widget, and adds it to the MDI area.
*
* \param widget the widget to add to the MDI area
* \param windowFlags flags used to customize the frame of the created subwindow
* \return the PipelineMdiSubWindow instance that was added to the MDI area
* \return the MdiDockableWindow instance that was added to the MDI area
*/
MdiDockedWindow* addSubWindow(QWidget* widget, Qt::WindowFlags windowFlags = 0);
MdiDockableWindow* addWidget(QWidget* widget, Qt::WindowFlags windowFlags = 0);
/**
* Add \p mdiSubWindow to the MDI area.
*
* \param mdiSubWindow the MDI sub-window to be added to the MDI area
* \return the MDI sub-window that was passed in
*/
QMdiSubWindow* addSubWindow(QMdiSubWindow* mdiSubWindow);
private slots:
/**
* Track the position of a floating MDI window and dock it if necessary.
* Remove \p mdiSubWindow from the MDI area.
*
* This slot is invoked when the position of a floating MDI window changes.
* \param mdiSubWindow the MDI sub-window to be removed from the MDI area
*/
void trackFloatingWindowsPosition(MdiFloatingWindow* floatingWindow, const QPoint& newPos);
void removeSubWindow(QMdiSubWindow* mdiSubWindow);
/**
* Track the position of a docked MDI window and detach it if necessary.
* Return a menu that lets the user control how the area's contents are displayed.
*
* This slot is invoked when the position of an MDI subwindow changes.
* The menu contains actions that make it possible to change the layout and visibility of
* the area's subwindows.
*/
void trackMdiSubWindowsPosition(MdiDockedWindow* mdiSubWindow, const QPoint& newPos);
QMenu* menu() const;
private slots:
/**
* Display docked windows as sub-windows with window frames.
*/
void switchToTiledDisplay();
/**
* Display docked windows with tabs in a tab bar.
*/
void switchToTabbedDisplay();
private:
QMenu* _menu; ///< Menu with actions for controlling the MDI area and its subwindows.
};
}
......