From 66a80059486fc54d87aece98d2de6f9e5da27533 Mon Sep 17 00:00:00 2001 From: Joshua Goins Date: Mon, 24 Dec 2018 10:04:00 -0500 Subject: [PATCH] Add ToolWindowManager --- 3rdparty/CMakeLists.txt | 1 + 3rdparty/ToolWindowManager/CMakeLists.txt | 24 + .../include/ToolWindowManager.h | 350 +++++ .../include/ToolWindowManagerArea.h | 123 ++ .../include/ToolWindowManagerSplitter.h | 47 + .../include/ToolWindowManagerTabBar.h | 102 ++ .../include/ToolWindowManagerWrapper.h | 111 ++ .../src/ToolWindowManager.cpp | 1235 +++++++++++++++++ .../src/ToolWindowManagerArea.cpp | 338 +++++ .../src/ToolWindowManagerSplitter.cpp | 72 + .../src/ToolWindowManagerTabBar.cpp | 382 +++++ .../src/ToolWindowManagerWrapper.cpp | 478 +++++++ tools/leveleditor/CMakeLists.txt | 2 +- tools/leveleditor/src/editorstyle.cpp | 47 +- tools/leveleditor/src/mainwindow.cpp | 9 +- 15 files changed, 3309 insertions(+), 12 deletions(-) create mode 100644 3rdparty/ToolWindowManager/CMakeLists.txt create mode 100644 3rdparty/ToolWindowManager/include/ToolWindowManager.h create mode 100644 3rdparty/ToolWindowManager/include/ToolWindowManagerArea.h create mode 100644 3rdparty/ToolWindowManager/include/ToolWindowManagerSplitter.h create mode 100644 3rdparty/ToolWindowManager/include/ToolWindowManagerTabBar.h create mode 100644 3rdparty/ToolWindowManager/include/ToolWindowManagerWrapper.h create mode 100644 3rdparty/ToolWindowManager/src/ToolWindowManager.cpp create mode 100644 3rdparty/ToolWindowManager/src/ToolWindowManagerArea.cpp create mode 100644 3rdparty/ToolWindowManager/src/ToolWindowManagerSplitter.cpp create mode 100644 3rdparty/ToolWindowManager/src/ToolWindowManagerTabBar.cpp create mode 100644 3rdparty/ToolWindowManager/src/ToolWindowManagerWrapper.cpp diff --git a/3rdparty/CMakeLists.txt b/3rdparty/CMakeLists.txt index 6e94f78..a623d9c 100644 --- a/3rdparty/CMakeLists.txt +++ b/3rdparty/CMakeLists.txt @@ -2,3 +2,4 @@ add_subdirectory(imgui) add_subdirectory(nlohmann) add_subdirectory(stb) add_subdirectory(smaa) +add_subdirectory(ToolWindowManager) diff --git a/3rdparty/ToolWindowManager/CMakeLists.txt b/3rdparty/ToolWindowManager/CMakeLists.txt new file mode 100644 index 0000000..59b2d63 --- /dev/null +++ b/3rdparty/ToolWindowManager/CMakeLists.txt @@ -0,0 +1,24 @@ +find_package(Qt5Core REQUIRED) +find_package(Qt5Gui REQUIRED) +find_package(Qt5Widgets REQUIRED) + +set(SOURCE_FILES + src/ToolWindowManager.cpp + src/ToolWindowManagerArea.cpp + src/ToolWindowManagerWrapper.cpp + src/ToolWindowManagerSplitter.cpp + src/ToolWindowManagerTabBar.cpp) + +set(INCLUDE_FILES + include/ToolWindowManager.h + include/ToolWindowManagerArea.h + include/ToolWindowManagerWrapper.h + include/ToolWindowManagerSplitter.h + include/ToolWindowManagerTabBar.h) + +qt5_wrap_cpp(TWM_SRC ${INCLUDE_FILES}) + +add_library(ToolWindowManager ${SOURCE_FILES} ${TWM_SRC}) +target_include_directories(ToolWindowManager PUBLIC include) + +qt5_use_modules(ToolWindowManager Core Gui Widgets) diff --git a/3rdparty/ToolWindowManager/include/ToolWindowManager.h b/3rdparty/ToolWindowManager/include/ToolWindowManager.h new file mode 100644 index 0000000..76522f7 --- /dev/null +++ b/3rdparty/ToolWindowManager/include/ToolWindowManager.h @@ -0,0 +1,350 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2014 Pavel Strakhov + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ +#ifndef TOOLWINDOWMANAGER_H +#define TOOLWINDOWMANAGER_H + +#include +#include +#include +#include + +#include + +class ToolWindowManagerArea; +class ToolWindowManagerWrapper; + +class QLabel; +class QSplitter; + +/*! + * \brief The ToolWindowManager class provides docking tool behavior. + * + * The behavior is similar to tool windows mechanism in Visual Studio or Eclipse. + * User can arrange tool windows + * in tabs, dock it to any border, split with vertical and horizontal splitters, + * tabify them together and detach to floating windows. + * + * See https://github.com/Riateche/toolwindowmanager for detailed description. + */ +class ToolWindowManager : public QWidget { + Q_OBJECT + /*! + * \brief Whether or not to allow floating windows to be created. + * + * Default value is to allow it. + * + * Access functions: allowFloatingWindow, setAllowFloatingWindow. + * + */ + Q_PROPERTY(int allowFloatingWindow READ allowFloatingWindow WRITE setAllowFloatingWindow) + + /*! + * \brief How much of a margin should be placed between drop hotspots. + * + * Default value is 4. + * + * Access functions: dropHotspotMargin, setDropHotspotMargin. + * + */ + Q_PROPERTY(int dropHotspotMargin READ dropHotspotMargin WRITE setDropHotspotMargin) + + /*! + * \brief How wide and heigh each drop hotspot icon should be drawn at, in pixels. + * + * Default value is 32. + * + * Access functions: dropHotspotDimension, setDropHotspotDimension. + * + */ + Q_PROPERTY(int dropHotspotDimension READ dropHotspotDimension WRITE setDropHotspotDimension) + +public: + /*! + * \brief Creates a manager with given \a parent. + */ + explicit ToolWindowManager(QWidget *parent = 0); + /*! + * \brief Destroys the widget. Additionally all tool windows and all floating windows + * created by this widget are destroyed. + */ + virtual ~ToolWindowManager(); + + //! Toolwindow properties + enum ToolWindowProperty { + //! Disables all drag/docking ability by the user + DisallowUserDocking = 0x1, + //! Hides the close button on the tab for this tool window + HideCloseButton = 0x2, + //! Disable the user being able to drag this tab in the tab bar, to rearrange + DisableDraggableTab = 0x4, + //! When the tool window is closed, hide it instead of removing it + HideOnClose = 0x8, + //! Don't allow this tool window to be floated + DisallowFloatWindow = 0x10, + //! When displaying this tool window in tabs, always display the tabs even if there's only one + AlwaysDisplayFullTabs = 0x20, + }; + + //! Type of AreaReference. + enum AreaReferenceType { + //! The area tool windows has been added to most recently. + LastUsedArea, + //! New area in a detached window. + NewFloatingArea, + //! Area inside the manager widget (only available when there is no tool windows in it). + EmptySpace, + //! Tool window is hidden. + NoArea, + //! Existing area specified in AreaReference argument. + AddTo, + //! New area to the left of the area specified in AreaReference argument. + LeftOf, + //! New area to the right of the area specified in AreaReference argument. + RightOf, + //! New area to the top of the area specified in AreaReference argument. + TopOf, + //! New area to the bottom of the area specified in AreaReference argument. + BottomOf, + //! New area to the left of the window containing the specified in AreaReference argument. + LeftWindowSide, + //! New area to the right of the window containing the specified in AreaReference argument. + RightWindowSide, + //! New area to the top of the window containing the specified in AreaReference argument. + TopWindowSide, + //! New area to the bottom of the window containing the specified in AreaReference argument. + BottomWindowSide, + //! Invalid value, just indicates the number of types available + NumReferenceTypes + }; + + /*! + * \brief The AreaReference class represents a place where tool windows should be moved. + */ + class AreaReference { + public: + /*! + * Creates an area reference of the given \a type. If \a type requires specifying + * area, it should be given in \a area argument. Otherwise \a area should have default value (0). + */ + AreaReference(AreaReferenceType type = NoArea, ToolWindowManagerArea* area = 0, float percentage = 0.5f); + //! Returns type of the reference. + AreaReferenceType type() const { return m_type; } + //! Returns area of the reference, or 0 if it was not specified. + ToolWindowManagerArea* area() const; + + private: + AreaReferenceType m_type; + QWidget* m_widget; + float m_percentage; + bool dragResult; + QWidget* widget() const { return m_widget; } + float percentage() const { return m_percentage; } + AreaReference(AreaReferenceType type, QWidget* widget); + void setWidget(QWidget* widget); + + friend class ToolWindowManager; + + }; + + /*! + * Adds \a toolWindow to the manager and moves it to the position specified by + * \a area. This function is a shortcut for ToolWindowManager::addToolWindows. + */ + void addToolWindow(QWidget* toolWindow, const AreaReference& area); + + /*! + * Sets the set of \a properties on \a toolWindow that is already added to the manager. + */ + void setToolWindowProperties(QWidget* toolWindow, ToolWindowProperty properties); + + /*! + * Returns the set of \a properties on \a toolWindow. + */ + ToolWindowProperty toolWindowProperties(QWidget* toolWindow); + + /*! + * \brief Adds \a toolWindows to the manager and moves it to the position specified by + * \a area. + * The manager takes ownership of the tool windows and will delete them upon destruction. + * + * toolWindow->windowIcon() and toolWindow->windowTitle() will be used as the icon and title + * of the tab that represents the tool window. + * + * If you intend to use ToolWindowManager::saveState + * and ToolWindowManager::restoreState functions, you must set objectName() of each added + * tool window to a non-empty unique string. + */ + void addToolWindows(QList toolWindows, const AreaReference& area); + + /*! + * Returns area that contains \a toolWindow, or 0 if \a toolWindow is hidden. + */ + ToolWindowManagerArea* areaOf(QWidget* toolWindow); + + /*! + * \brief Moves \a toolWindow to the position specified by \a area. + * + * \a toolWindow must be added to the manager prior to calling this function. + */ + void moveToolWindow(QWidget* toolWindow, AreaReference area); + + /*! + * \brief Moves \a toolWindows to the position specified by \a area. + * + * \a toolWindows must be added to the manager prior to calling this function. + */ + void moveToolWindows(QList toolWindows, AreaReference area); + + /*! + * \brief Removes \a toolWindow from the manager. \a toolWindow becomes a hidden + * top level widget. The ownership of \a toolWindow is returned to the caller. + */ + void removeToolWindow(QWidget* toolWindow); + + /*! + * \brief Returns all tool window added to the manager. + */ + const QList& toolWindows() { return m_toolWindows; } + + /*! + * Hides \a toolWindow. + * + * \a toolWindow must be added to the manager prior to calling this function. + */ + void hideToolWindow(QWidget* toolWindow) { moveToolWindow(toolWindow, NoArea); } + + static ToolWindowManager* managerOf(QWidget* toolWindow); + static void closeToolWindow(QWidget *toolWindow); + static void raiseToolWindow(QWidget *toolWindow); + + /*! + * \brief saveState + */ + QVariantMap saveState(); + + /*! + * \brief restoreState + */ + void restoreState(const QVariantMap& data); + + typedef std::function CreateCallback; + + void setToolWindowCreateCallback(const CreateCallback &cb) { m_createCallback = cb; } + QWidget *createToolWindow(const QString& objectName); + + void setHotspotPixmap(AreaReferenceType ref, const QPixmap &pix) { m_pixmaps[ref] = pix; } + + void setDropHotspotMargin(int pixels); + bool dropHotspotMargin() { return m_dropHotspotMargin; } + + void setDropHotspotDimension(int pixels); + bool dropHotspotDimension() { return m_dropHotspotDimension; } + + /*! \cond PRIVATE */ + void setAllowFloatingWindow(bool pixels); + bool allowFloatingWindow() { return m_allowFloatingWindow; } + /*! \endcond */ + + +signals: + /*! + * \brief This signal is emitted when \a toolWindow may be hidden or shown. + * \a visible indicates new visibility state of the tool window. + */ + void toolWindowVisibilityChanged(QWidget* toolWindow, bool visible); + +private: + QList m_toolWindows; // all added tool windows + QHash m_toolWindowProperties; // all tool window properties + QList m_areas; // all areas for this manager + QList m_wrappers; // all wrappers for this manager + // list of tool windows that are currently dragged, or empty list if there is no current drag + QList m_draggedToolWindows; + ToolWindowManagerWrapper* m_draggedWrapper; // the wrapper if a whole float window is being dragged + ToolWindowManagerArea* m_hoverArea; // the area currently being hovered over in a drag + // a semi-transparent preview of where the dragged toolwindow(s) will be docked + QWidget* m_previewOverlay; + QWidget* m_previewTabOverlay; + QLabel* m_dropHotspots[NumReferenceTypes]; + QPixmap m_pixmaps[NumReferenceTypes]; + + bool m_allowFloatingWindow; // Allow floating windows from this docking area + int m_dropHotspotMargin; // The pixels between drop hotspot icons + int m_dropHotspotDimension; // The pixel dimension of the hotspot icons + + CreateCallback m_createCallback; + + ToolWindowManagerWrapper* wrapperOf(QWidget* toolWindow); + + void drawHotspotPixmaps(); + + bool allowClose(QWidget *toolWindow); + + // last widget used for adding tool windows, or 0 if there isn't one + // (warning: may contain pointer to deleted object) + ToolWindowManagerArea* m_lastUsedArea; + //remove tool window from its area (if any) and set parent to 0 + void releaseToolWindow(QWidget* toolWindow); + void simplifyLayout(); //remove constructions that became useless + void startDrag(const QList& toolWindows, ToolWindowManagerWrapper *wrapper); + + QVariantMap saveSplitterState(QSplitter* splitter); + QSplitter* restoreSplitterState(const QVariantMap& data); + + AreaReferenceType currentHotspot(); + + void updateDragPosition(); + void abortDrag(); + void finishDrag(); + bool dragInProgress() { return !m_draggedToolWindows.isEmpty(); } + + friend class ToolWindowManagerArea; + friend class ToolWindowManagerWrapper; + +protected: + //! Event filter for grabbing and processing drag aborts. + virtual bool eventFilter(QObject *object, QEvent *event); + + /*! + * \brief Creates new splitter and sets its default properties. You may reimplement + * this function to change properties of all splitters used by this class. + */ + virtual QSplitter* createSplitter(); + /*! + * \brief Creates new area and sets its default properties. You may reimplement + * this function to change properties of all tab widgets used by this class. + */ + virtual ToolWindowManagerArea *createArea(); + +private slots: + void tabCloseRequested(int index); + void windowTitleChanged(const QString &title); + +}; + +inline ToolWindowManager::ToolWindowProperty operator|(ToolWindowManager::ToolWindowProperty a, ToolWindowManager::ToolWindowProperty b) +{ return ToolWindowManager::ToolWindowProperty(int(a) | int(b)); } + +#endif // TOOLWINDOWMANAGER_H diff --git a/3rdparty/ToolWindowManager/include/ToolWindowManagerArea.h b/3rdparty/ToolWindowManager/include/ToolWindowManagerArea.h new file mode 100644 index 0000000..c9b3fe8 --- /dev/null +++ b/3rdparty/ToolWindowManager/include/ToolWindowManagerArea.h @@ -0,0 +1,123 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2014 Pavel Strakhov + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ +#ifndef TOOLWINDOWMANAGERAREA_H +#define TOOLWINDOWMANAGERAREA_H + +#include +#include + +class ToolWindowManager; +class ToolWindowManagerTabBar; + +/*! + * \brief The ToolWindowManagerArea class is a tab widget used to store tool windows. + * It implements dragging of its tab or the whole tab widget. + */ +class ToolWindowManagerArea : public QTabWidget { + Q_OBJECT +public: + //! Creates new area. + explicit ToolWindowManagerArea(ToolWindowManager* manager, QWidget *parent = 0); + //! Destroys the area. + virtual ~ToolWindowManagerArea(); + + /*! + * Add \a toolWindow to this area. + */ + void addToolWindow(QWidget* toolWindow, int insertIndex = -1); + + /*! + * Add \a toolWindows to this area. + */ + void addToolWindows(const QList& toolWindows, int insertIndex = -1); + + void enableUserDrop() { m_userCanDrop = true; } + void disableUserDrop() { m_userCanDrop = false; } + + bool allowUserDrop() { return m_userCanDrop; } + + /*! + * Returns a list of all tool windows in this area. + */ + QList toolWindows(); + + ToolWindowManager* manager() { return m_manager; } + + /*! + * Updates the \a toolWindow to its current properties and title. + */ + void updateToolWindow(QWidget* toolWindow); + +protected: + //! Reimplemented from QTabWidget::mouseMoveEvent. + virtual void mouseMoveEvent(QMouseEvent *); + //! Reimplemented from QTabWidget::eventFilter. + virtual bool eventFilter(QObject *object, QEvent *event); + + //! Reimplemented from QTabWidget::tabInserted. + virtual void tabInserted(int index); + //! Reimplemented from QTabWidget::tabRemoved. + virtual void tabRemoved(int index); + +private: + ToolWindowManager* m_manager; + ToolWindowManagerTabBar* m_tabBar; + bool m_dragCanStart; // indicates that user has started mouse movement on QTabWidget + // that can be considered as dragging it if the cursor will leave + // its area + QPoint m_dragCanStartPos; // the position the cursor was at + + bool m_tabDragCanStart; // indicates that user has started mouse movement on QTabWidget + // that can be considered as dragging current tab + // if the cursor will leave the tab bar area + + bool m_userCanDrop; // indictes the user is allowed to drop things on this area + + bool m_inTabMoved; // if we're in the tabMoved() function (so if we call tabMove to cancel + // the movement, we shouldn't re-check the tabMoved behaviour) + + QVector m_tabSelectOrder; // This is the 'history' order of the tabs as they were selected, + // with most recently selected index last. Any time a tab is closed + // we select the last one on the list. + + QVariantMap saveState(); // dump contents to variable + void restoreState(const QVariantMap& data); //restore contents from given variable + + //check if mouse left tab widget area so that dragging should start + void check_mouse_move(); + + bool useMinimalTabBar(); + + friend class ToolWindowManager; + friend class ToolWindowManagerTabBar; + friend class ToolWindowManagerWrapper; + +private slots: + void tabMoved(int from, int to); + void tabSelected(int index); + void tabClosing(int index); +}; + +#endif // TOOLWINDOWMANAGERAREA_H diff --git a/3rdparty/ToolWindowManager/include/ToolWindowManagerSplitter.h b/3rdparty/ToolWindowManager/include/ToolWindowManagerSplitter.h new file mode 100644 index 0000000..95b9c2f --- /dev/null +++ b/3rdparty/ToolWindowManager/include/ToolWindowManagerSplitter.h @@ -0,0 +1,47 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017 Baldur Karlsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ +#ifndef TOOLWINDOWMANAGERSPLITTER_H +#define TOOLWINDOWMANAGERSPLITTER_H + +#include + +/*! + * \brief The ToolWindowManagerSplitter class is a splitter that tweaks how sizes are allocated in + * children when a child is removed. + */ +class ToolWindowManagerSplitter : public QSplitter { + Q_OBJECT +public: + //! Creates new tab bar. + explicit ToolWindowManagerSplitter(QWidget *parent = 0); + //! Destroys the tab bar. + virtual ~ToolWindowManagerSplitter(); + +protected: + //! Reimplemented from QSplitter to share excess space differently. + void childEvent(QChildEvent *) Q_DECL_OVERRIDE; +}; + +#endif // TOOLWINDOWMANAGERSPLITTER_H diff --git a/3rdparty/ToolWindowManager/include/ToolWindowManagerTabBar.h b/3rdparty/ToolWindowManager/include/ToolWindowManagerTabBar.h new file mode 100644 index 0000000..99f56d8 --- /dev/null +++ b/3rdparty/ToolWindowManager/include/ToolWindowManagerTabBar.h @@ -0,0 +1,102 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017 Baldur Karlsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ +#ifndef TOOLWINDOWMANAGERTABBAR_H +#define TOOLWINDOWMANAGERTABBAR_H + +#include +#include + +class ToolWindowManager; +class ToolWindowManagerArea; + +/*! + * \brief The ToolWindowManagerTabBar class is a tab bar used to customise the painting + * in the case that there's only only one child widget. + */ +class ToolWindowManagerTabBar : public QTabBar { + Q_OBJECT +public: + //! Creates new tab bar. + explicit ToolWindowManagerTabBar(QWidget *parent = 0); + //! Destroys the tab bar. + virtual ~ToolWindowManagerTabBar(); + + bool tabsClosable() const { return m_tabsClosable; } + void setTabsClosable(bool closable) { m_tabsClosable = closable; updateClosable(); } + + //! Reimplemented from QTabWidget::QTabBar to custom size for the single tab case. + QSize sizeHint() const Q_DECL_OVERRIDE; + + bool useMinimalBar() const; + + QSize minimumSizeHint() const Q_DECL_OVERRIDE; + + //! is this point in a custom titlebar button + bool inButton(QPoint pos); + +protected: + //! Reimplemented from QTabWidget::QTabBar to custom paint for the single tab case. + void paintEvent(QPaintEvent *) Q_DECL_OVERRIDE; + + //! Reimplemented from QTabWidget::QTabBar to cache painting parameters + void resizeEvent(QResizeEvent *) Q_DECL_OVERRIDE; + //! Reimplemented from QTabWidget::QTabBar to implement hover/click status of buttons + void mousePressEvent(QMouseEvent *) Q_DECL_OVERRIDE; + void mouseMoveEvent(QMouseEvent *) Q_DECL_OVERRIDE; + void mouseReleaseEvent(QMouseEvent *) Q_DECL_OVERRIDE; + void leaveEvent(QEvent *) Q_DECL_OVERRIDE; + + //! Reimplemented from QTabWidget::QTabBar to enable/disable 'real' closable tabs. + virtual void tabInserted(int index) Q_DECL_OVERRIDE; + virtual void tabRemoved(int index) Q_DECL_OVERRIDE; + + ToolWindowManagerArea* m_area; + + bool m_tabsClosable; + + struct ButtonData + { + QRect rect; + QIcon icon; + bool clicked; + bool hover; + + bool operator ==(const ButtonData &o) { + return rect == o.rect && clicked == o.clicked && hover == o.hover; + } + + bool operator !=(const ButtonData &o) { + return !(*this == o); + } + + } m_close, m_pin; + + QRect m_titleRect; + + void updateClosable(); + bool floatingWindowChild() const; +}; + +#endif // TOOLWINDOWMANAGERTABBAR_H diff --git a/3rdparty/ToolWindowManager/include/ToolWindowManagerWrapper.h b/3rdparty/ToolWindowManager/include/ToolWindowManagerWrapper.h new file mode 100644 index 0000000..339d7cf --- /dev/null +++ b/3rdparty/ToolWindowManager/include/ToolWindowManagerWrapper.h @@ -0,0 +1,111 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2014 Pavel Strakhov + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ +#ifndef TOOLWINDOWMANAGERWRAPPER_H +#define TOOLWINDOWMANAGERWRAPPER_H + +#include +#include +#include + +class ToolWindowManager; +class QLabel; + +/*! + * \brief The ToolWindowManagerWrapper class is used by ToolWindowManager to wrap its content. + * One wrapper is a direct child of the manager and contains tool windows that are inside its window. + * All other wrappers are top level floating windows that contain detached tool windows. + * + */ +class ToolWindowManagerWrapper : public QWidget { + Q_OBJECT +public: + //! Creates new wrapper. + explicit ToolWindowManagerWrapper(ToolWindowManager* manager, bool floating); + //! Removes the wrapper. + virtual ~ToolWindowManagerWrapper(); + + ToolWindowManager* manager() { return m_manager; } + + bool floating() { return m_floating; } + + void updateTitle(); + +protected: + //! Reimplemented to register hiding of contained tool windows when user closes the floating window. + virtual void closeEvent(QCloseEvent *) Q_DECL_OVERRIDE; + + //! Event filter for grabbing and processing mouse drags as toolwindow drags. + virtual bool eventFilter(QObject *object, QEvent *event) Q_DECL_OVERRIDE; + + //! Painting and resizing for custom-rendered widget frames + virtual void paintEvent(QPaintEvent *) Q_DECL_OVERRIDE; + virtual void resizeEvent(QResizeEvent *) Q_DECL_OVERRIDE; + +private: + ToolWindowManager* m_manager; + + enum class ResizeDirection { + NW, + NE, + SW, + SE, + N, + E, + S, + W, + Count, + }; + + QRect titleRect(); + ResizeDirection checkResize(); + + QRect m_closeRect; + QIcon m_closeIcon; + int m_closeButtonSize; + int m_titleHeight; + int m_frameWidth; + bool m_floating; + + QTimer* m_moveTimeout; + + bool m_dragReady; // we've clicked and started moving but haven't moved enough yet + QPoint m_dragStartCursor; // cursor at the click to start a drag + QRect m_dragStartGeometry; // window geometry at the click to start a drag + bool m_dragActive; // whether a drag currently on-going + ResizeDirection m_dragDirection; // the current direction being dragged + + //dump content's layout to variable + QVariantMap saveState(); + + //construct layout based on given dump + void restoreState(const QVariantMap& data); + + friend class ToolWindowManager; + +private slots: + void moveTimeout(); +}; + +#endif // TOOLWINDOWMANAGERWRAPPER_H diff --git a/3rdparty/ToolWindowManager/src/ToolWindowManager.cpp b/3rdparty/ToolWindowManager/src/ToolWindowManager.cpp new file mode 100644 index 0000000..7b52d17 --- /dev/null +++ b/3rdparty/ToolWindowManager/src/ToolWindowManager.cpp @@ -0,0 +1,1235 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2014 Pavel Strakhov + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ +#include "ToolWindowManager.h" +#include "ToolWindowManagerArea.h" +#include "ToolWindowManagerSplitter.h" +#include "ToolWindowManagerWrapper.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +template +T findClosestParent(QWidget* widget) { + while(widget) { + if (qobject_cast(widget)) { + return static_cast(widget); + } + widget = widget->parentWidget(); + } + return 0; +} + +ToolWindowManager::ToolWindowManager(QWidget *parent) : + QWidget(parent) +{ + QVBoxLayout* mainLayout = new QVBoxLayout(this); + mainLayout->setContentsMargins(0, 0, 0, 0); + ToolWindowManagerWrapper* wrapper = new ToolWindowManagerWrapper(this, false); + wrapper->setWindowFlags(wrapper->windowFlags() & ~Qt::Tool); + mainLayout->addWidget(wrapper); + m_allowFloatingWindow = true; + m_createCallback = NULL; + m_lastUsedArea = NULL; + + m_draggedWrapper = NULL; + m_hoverArea = NULL; + + QPalette pal = palette(); + pal.setColor(QPalette::Background, pal.color(QPalette::Highlight)); + + m_previewOverlay = new QWidget(NULL); + m_previewOverlay->setAutoFillBackground(true); + m_previewOverlay->setPalette(pal); + m_previewOverlay->setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint); + m_previewOverlay->setWindowOpacity(0.3); + m_previewOverlay->setAttribute(Qt::WA_ShowWithoutActivating); + m_previewOverlay->setAttribute(Qt::WA_AlwaysStackOnTop); + m_previewOverlay->hide(); + + m_previewTabOverlay = new QWidget(NULL); + m_previewTabOverlay->setAutoFillBackground(true); + m_previewTabOverlay->setPalette(pal); + m_previewTabOverlay->setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint); + m_previewTabOverlay->setWindowOpacity(0.3); + m_previewTabOverlay->setAttribute(Qt::WA_ShowWithoutActivating); + m_previewTabOverlay->setAttribute(Qt::WA_AlwaysStackOnTop); + m_previewTabOverlay->hide(); + + + for (int i=0; i < NumReferenceTypes; i++) + m_dropHotspots[i] = NULL; + + m_dropHotspotDimension = 32; + m_dropHotspotMargin = 4; + + drawHotspotPixmaps(); + + for (AreaReferenceType type : { AddTo, + TopOf, LeftOf, + RightOf, BottomOf, + TopWindowSide, LeftWindowSide, + RightWindowSide, BottomWindowSide }) { + m_dropHotspots[type] = new QLabel(NULL); + m_dropHotspots[type]->setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint); + m_dropHotspots[type]->setAttribute(Qt::WA_ShowWithoutActivating); + m_dropHotspots[type]->setAttribute(Qt::WA_AlwaysStackOnTop); + m_dropHotspots[type]->setPixmap(m_pixmaps[type]); + m_dropHotspots[type]->setFixedSize(m_dropHotspotDimension, m_dropHotspotDimension); + } +} + +ToolWindowManager::~ToolWindowManager() { + delete m_previewOverlay; + delete m_previewTabOverlay; + for(QWidget *hotspot : m_dropHotspots) + delete hotspot; + while(!m_areas.isEmpty()) { + delete m_areas.first(); + } + while(!m_wrappers.isEmpty()) { + delete m_wrappers.first(); + } +} + +void ToolWindowManager::setToolWindowProperties(QWidget* toolWindow, ToolWindowManager::ToolWindowProperty properties) { + m_toolWindowProperties[toolWindow] = properties; + ToolWindowManagerArea *area = areaOf(toolWindow); + if(area) + area->updateToolWindow(toolWindow); +} + +ToolWindowManager::ToolWindowProperty ToolWindowManager::toolWindowProperties(QWidget* toolWindow) { + return m_toolWindowProperties[toolWindow]; +} + +void ToolWindowManager::addToolWindow(QWidget *toolWindow, const AreaReference &area) { + addToolWindows(QList() << toolWindow, area); +} + +void ToolWindowManager::addToolWindows(QList toolWindows, const ToolWindowManager::AreaReference &area) { + foreach(QWidget* toolWindow, toolWindows) { + if (!toolWindow) { + qWarning("cannot add null widget"); + continue; + } + if (m_toolWindows.contains(toolWindow)) { + qWarning("this tool window has already been added"); + continue; + } + toolWindow->hide(); + toolWindow->setParent(0); + m_toolWindows << toolWindow; + m_toolWindowProperties[toolWindow] = ToolWindowProperty(0); + QObject::connect(toolWindow, &QWidget::windowTitleChanged, this, &ToolWindowManager::windowTitleChanged); + } + moveToolWindows(toolWindows, area); +} + +ToolWindowManagerArea *ToolWindowManager::areaOf(QWidget *toolWindow) { + return findClosestParent(toolWindow); +} + +ToolWindowManagerWrapper *ToolWindowManager::wrapperOf(QWidget *toolWindow) { + return findClosestParent(toolWindow); +} + +void ToolWindowManager::moveToolWindow(QWidget *toolWindow, AreaReference area) { + moveToolWindows(QList() << toolWindow, area); +} + +void ToolWindowManager::moveToolWindows(QList toolWindows, + ToolWindowManager::AreaReference area) { + QList wrappersToUpdate; + foreach(QWidget* toolWindow, toolWindows) { + if (!m_toolWindows.contains(toolWindow)) { + qWarning("unknown tool window"); + return; + } + ToolWindowManagerWrapper *oldWrapper = wrapperOf(toolWindow); + if (toolWindow->parentWidget() != 0) { + releaseToolWindow(toolWindow); + } + if (oldWrapper && !wrappersToUpdate.contains(oldWrapper)) + wrappersToUpdate.push_back(oldWrapper); + } + if (area.type() == LastUsedArea && !m_lastUsedArea) { + ToolWindowManagerArea* foundArea = findChild(); + if (foundArea) { + area = AreaReference(AddTo, foundArea); + } else { + area = EmptySpace; + } + } + + if (area.type() == NoArea) { + //do nothing + } else if (area.type() == NewFloatingArea) { + ToolWindowManagerArea* floatArea = createArea(); + floatArea->addToolWindows(toolWindows); + ToolWindowManagerWrapper* wrapper = new ToolWindowManagerWrapper(this, true); + wrapper->layout()->addWidget(floatArea); + wrapper->move(QCursor::pos()); + wrapper->updateTitle(); + wrapper->show(); + } else if (area.type() == AddTo) { + int idx = -1; + if (area.dragResult) { + idx = area.area()->tabBar()->tabAt(area.area()->tabBar()->mapFromGlobal(QCursor::pos())); + } + area.area()->addToolWindows(toolWindows, idx); + } else if (area.type() == LeftWindowSide || area.type() == RightWindowSide || + area.type() == TopWindowSide || area.type() == BottomWindowSide) { + ToolWindowManagerWrapper* wrapper = findClosestParent(area.area()); + if (!wrapper) { + qWarning("couldn't find wrapper"); + return; + } + + if (wrapper->layout()->count() > 1) + { + qWarning("wrapper has multiple direct children"); + return; + } + + QLayoutItem* item = wrapper->layout()->takeAt(0); + + QSplitter* splitter = createSplitter(); + if (area.type() == TopWindowSide || area.type() == BottomWindowSide) { + splitter->setOrientation(Qt::Vertical); + } else { + splitter->setOrientation(Qt::Horizontal); + } + + splitter->addWidget(item->widget()); + area.widget()->show(); + + delete item; + + ToolWindowManagerArea* newArea = createArea(); + newArea->addToolWindows(toolWindows); + + if (area.type() == TopWindowSide || area.type() == LeftWindowSide) { + splitter->insertWidget(0, newArea); + } else { + splitter->addWidget(newArea); + } + + wrapper->layout()->addWidget(splitter); + } else if (area.type() == LeftOf || area.type() == RightOf || + area.type() == TopOf || area.type() == BottomOf) { + QSplitter* parentSplitter = qobject_cast(area.widget()->parentWidget()); + ToolWindowManagerWrapper* wrapper = qobject_cast(area.widget()->parentWidget()); + if (!parentSplitter && !wrapper) { + qWarning("unknown parent type"); + return; + } + bool useParentSplitter = false; + int indexInParentSplitter = 0; + QList parentSplitterSizes; + if (parentSplitter) { + indexInParentSplitter = parentSplitter->indexOf(area.widget()); + parentSplitterSizes = parentSplitter->sizes(); + if (parentSplitter->orientation() == Qt::Vertical) { + useParentSplitter = area.type() == TopOf || area.type() == BottomOf; + } else { + useParentSplitter = area.type() == LeftOf || area.type() == RightOf; + } + } + if (useParentSplitter) { + int insertIndex = indexInParentSplitter; + if (area.type() == BottomOf || area.type() == RightOf) { + insertIndex++; + } + ToolWindowManagerArea* newArea = createArea(); + newArea->addToolWindows(toolWindows); + parentSplitter->insertWidget(insertIndex, newArea); + + if(parentSplitterSizes.count() > indexInParentSplitter && parentSplitterSizes[0] != 0) { + int availSize = parentSplitterSizes[indexInParentSplitter]; + + parentSplitterSizes[indexInParentSplitter] = int(availSize * (1.0f - area.percentage())); + parentSplitterSizes.insert(indexInParentSplitter, int(availSize * area.percentage())); + + parentSplitter->setSizes(parentSplitterSizes); + } + } else { + area.widget()->hide(); + area.widget()->setParent(0); + QSplitter* splitter = createSplitter(); + if (area.type() == TopOf || area.type() == BottomOf) { + splitter->setOrientation(Qt::Vertical); + } else { + splitter->setOrientation(Qt::Horizontal); + } + + ToolWindowManagerArea* newArea = createArea(); + + // inherit the size policy from the widget we are wrapping + splitter->setSizePolicy(area.widget()->sizePolicy()); + + // store old geometries so we can restore them + QRect areaGeometry = area.widget()->geometry(); + QRect newGeometry = newArea->geometry(); + + splitter->addWidget(area.widget()); + area.widget()->show(); + + if (area.type() == TopOf || area.type() == LeftOf) { + splitter->insertWidget(0, newArea); + } else { + splitter->addWidget(newArea); + } + + if (parentSplitter) { + parentSplitter->insertWidget(indexInParentSplitter, splitter); + + if (parentSplitterSizes.count() > 0 && parentSplitterSizes[0] != 0) { + parentSplitter->setSizes(parentSplitterSizes); + } + + } else { + wrapper->layout()->addWidget(splitter); + } + + newArea->addToolWindows(toolWindows); + + area.widget()->setGeometry(areaGeometry); + newArea->setGeometry(newGeometry); + + // Convert area percentage desired to relative sizes. + const int totalStretch = + (area.type() == TopOf || area.type() == BottomOf) + ? areaGeometry.height() : areaGeometry.width(); + int pct = int(totalStretch*area.percentage()); + + int a = pct; + int b = totalStretch-pct; + + if (area.type() == BottomOf || area.type() == RightOf) + std::swap(a, b); + + splitter->setSizes({a, b}); + } + } else if (area.type() == EmptySpace) { + ToolWindowManagerArea* newArea = createArea(); + findChild()->layout()->addWidget(newArea); + newArea->addToolWindows(toolWindows); + } else if (area.type() == LastUsedArea) { + m_lastUsedArea->addToolWindows(toolWindows); + } else { + qWarning("invalid type"); + } + simplifyLayout(); + foreach(QWidget* toolWindow, toolWindows) { + emit toolWindowVisibilityChanged(toolWindow, toolWindow->parent() != 0); + ToolWindowManagerWrapper* wrapper = wrapperOf(toolWindow); + if (wrapper && !wrappersToUpdate.contains(wrapper)) + wrappersToUpdate.push_back(wrapper); + } + foreach(ToolWindowManagerWrapper* wrapper, wrappersToUpdate) { + wrapper->updateTitle(); + } +} + +void ToolWindowManager::removeToolWindow(QWidget *toolWindow) { + if (!m_toolWindows.contains(toolWindow)) { + qWarning("unknown tool window"); + return; + } + + // search up to find the first parent manager + ToolWindowManager *manager = findClosestParent(toolWindow); + + if (!manager) { + qWarning("unknown tool window"); + return; + } + + if (!manager->allowClose(toolWindow)) + return; + + moveToolWindow(toolWindow, NoArea); + m_toolWindows.removeOne(toolWindow); + m_toolWindowProperties.remove(toolWindow); + delete toolWindow; +} + +ToolWindowManager* ToolWindowManager::managerOf(QWidget* toolWindow) { + if (!toolWindow) { + qWarning("NULL tool window"); + return NULL; + } + + return findClosestParent(toolWindow); +} + +void ToolWindowManager::closeToolWindow(QWidget *toolWindow) { + if (!toolWindow) { + qWarning("NULL tool window"); + return; + } + + // search up to find the first parent manager + ToolWindowManager *manager = findClosestParent(toolWindow); + + if(manager) { + manager->removeToolWindow(toolWindow); + return; + } + + qWarning("window not child of any tool window"); +} + +void ToolWindowManager::raiseToolWindow(QWidget *toolWindow) { + if (!toolWindow) { + qWarning("NULL tool window"); + return; + } + + // if the parent is a ToolWindowManagerArea, switch tabs + QWidget *parent = toolWindow->parentWidget(); + ToolWindowManagerArea *area = qobject_cast(parent); + if(area == NULL) + parent = parent->parentWidget(); + + area = qobject_cast(parent); + + if(area) + area->setCurrentWidget(toolWindow); + else + qWarning("parent is not a tool window area"); +} + +QWidget* ToolWindowManager::createToolWindow(const QString& objectName) +{ + if (m_createCallback) { + QWidget *toolWindow = m_createCallback(objectName); + if(toolWindow) { + m_toolWindows << toolWindow; + m_toolWindowProperties[toolWindow] = ToolWindowProperty(0); + QObject::connect(toolWindow, &QWidget::windowTitleChanged, this, &ToolWindowManager::windowTitleChanged); + return toolWindow; + } + } + + return NULL; +} + +void ToolWindowManager::setDropHotspotMargin(int pixels) { + m_dropHotspotMargin = pixels; + drawHotspotPixmaps(); +} + +void ToolWindowManager::setDropHotspotDimension(int pixels) { + m_dropHotspotDimension = pixels; + + for (QLabel *hotspot : m_dropHotspots) { + if(hotspot) + hotspot->setFixedSize(m_dropHotspotDimension, m_dropHotspotDimension); + } +} + +void ToolWindowManager::setAllowFloatingWindow(bool allow) { + m_allowFloatingWindow = allow; +} + +QVariantMap ToolWindowManager::saveState() { + QVariantMap result; + result[QStringLiteral("toolWindowManagerStateFormat")] = 1; + ToolWindowManagerWrapper* mainWrapper = findChild(); + if (!mainWrapper) { + qWarning("can't find main wrapper"); + return QVariantMap(); + } + result[QStringLiteral("mainWrapper")] = mainWrapper->saveState(); + QVariantList floatingWindowsData; + foreach(ToolWindowManagerWrapper* wrapper, m_wrappers) { + if (!wrapper->isWindow()) { continue; } + floatingWindowsData << wrapper->saveState(); + } + result[QStringLiteral("floatingWindows")] = floatingWindowsData; + return result; +} + +void ToolWindowManager::restoreState(const QVariantMap &dataMap) { + if (dataMap.isEmpty()) { return; } + if (dataMap[QStringLiteral("toolWindowManagerStateFormat")].toInt() != 1) { + qWarning("state format is not recognized"); + return; + } + moveToolWindows(m_toolWindows, NoArea); + ToolWindowManagerWrapper* mainWrapper = findChild(); + if (!mainWrapper) { + qWarning("can't find main wrapper"); + return; + } + mainWrapper->restoreState(dataMap[QStringLiteral("mainWrapper")].toMap()); + QVariantList floatWins = dataMap[QStringLiteral("floatingWindows")].toList(); + foreach(QVariant windowData, floatWins) { + ToolWindowManagerWrapper* wrapper = new ToolWindowManagerWrapper(this, true); + wrapper->restoreState(windowData.toMap()); + wrapper->updateTitle(); + wrapper->show(); + if(wrapper->windowState() & Qt::WindowMaximized) + { + wrapper->setWindowState(0); + wrapper->setWindowState(Qt::WindowMaximized); + } + } + simplifyLayout(); + foreach(QWidget* toolWindow, m_toolWindows) { + emit toolWindowVisibilityChanged(toolWindow, toolWindow->parentWidget() != 0); + } +} + +ToolWindowManagerArea *ToolWindowManager::createArea() { + ToolWindowManagerArea* area = new ToolWindowManagerArea(this, 0); + connect(area, SIGNAL(tabCloseRequested(int)), + this, SLOT(tabCloseRequested(int))); + return area; +} + +void ToolWindowManager::releaseToolWindow(QWidget *toolWindow) { + ToolWindowManagerArea* previousTabWidget = findClosestParent(toolWindow); + if (!previousTabWidget) { + qWarning("cannot find tab widget for tool window"); + return; + } + previousTabWidget->removeTab(previousTabWidget->indexOf(toolWindow)); + toolWindow->hide(); + toolWindow->setParent(0); + +} + +void ToolWindowManager::simplifyLayout() { + foreach(ToolWindowManagerArea* area, m_areas) { + if (area->parentWidget() == 0) { + if (area->count() == 0) { + if (area == m_lastUsedArea) { m_lastUsedArea = 0; } + //QTimer::singleShot(1000, area, SLOT(deleteLater())); + area->deleteLater(); + } + continue; + } + QSplitter* splitter = qobject_cast(area->parentWidget()); + QSplitter* validSplitter = 0; // least top level splitter that should remain + QSplitter* invalidSplitter = 0; //most top level splitter that should be deleted + while(splitter) { + if (splitter->count() > 1) { + validSplitter = splitter; + break; + } else { + invalidSplitter = splitter; + splitter = qobject_cast(splitter->parentWidget()); + } + } + if (!validSplitter) { + ToolWindowManagerWrapper* wrapper = findClosestParent(area); + if (!wrapper) { + qWarning("can't find wrapper"); + return; + } + if (area->count() == 0 && wrapper->isWindow()) { + wrapper->hide(); + // can't deleteLater immediately (strange MacOS bug) + //QTimer::singleShot(1000, wrapper, SLOT(deleteLater())); + wrapper->deleteLater(); + } else if (area->parent() != wrapper) { + wrapper->layout()->addWidget(area); + } + } else { + if (area->count() > 0) { + if (validSplitter && area->parent() != validSplitter) { + int index = validSplitter->indexOf(invalidSplitter); + validSplitter->insertWidget(index, area); + } + } + } + if (invalidSplitter) { + invalidSplitter->hide(); + invalidSplitter->setParent(0); + //QTimer::singleShot(1000, invalidSplitter, SLOT(deleteLater())); + invalidSplitter->deleteLater(); + } + if (area->count() == 0) { + area->hide(); + area->setParent(0); + if (area == m_lastUsedArea) { m_lastUsedArea = 0; } + //QTimer::singleShot(1000, area, SLOT(deleteLater())); + area->deleteLater(); + } + } +} + +void ToolWindowManager::startDrag(const QList &toolWindows, + ToolWindowManagerWrapper *wrapper) { + if (dragInProgress()) { + qWarning("ToolWindowManager::execDrag: drag is already in progress"); + return; + } + foreach(QWidget* toolWindow, toolWindows) { + if(toolWindowProperties(toolWindow) & DisallowUserDocking) { return; } + } + if (toolWindows.isEmpty()) { return; } + + m_draggedWrapper = wrapper; + m_draggedToolWindows = toolWindows; + qApp->installEventFilter(this); +} + +QVariantMap ToolWindowManager::saveSplitterState(QSplitter *splitter) { + QVariantMap result; + result[QStringLiteral("state")] = splitter->saveState().toBase64(); + result[QStringLiteral("type")] = QStringLiteral("splitter"); + QVariantList items; + for(int i = 0; i < splitter->count(); i++) { + QWidget* item = splitter->widget(i); + QVariantMap itemValue; + ToolWindowManagerArea* area = qobject_cast(item); + if (area) { + itemValue = area->saveState(); + } else { + QSplitter* childSplitter = qobject_cast(item); + if (childSplitter) { + itemValue = saveSplitterState(childSplitter); + } else { + qWarning("unknown splitter item"); + } + } + items << itemValue; + } + result[QStringLiteral("items")] = items; + return result; +} + +QSplitter *ToolWindowManager::restoreSplitterState(const QVariantMap &savedData) { + if (savedData[QStringLiteral("items")].toList().count() < 2) { + qWarning("invalid splitter encountered"); + } + QSplitter* splitter = createSplitter(); + + QVariantList itemList = savedData[QStringLiteral("items")].toList(); + foreach(QVariant itemData, itemList) { + QVariantMap itemValue = itemData.toMap(); + QString itemType = itemValue[QStringLiteral("type")].toString(); + if (itemType == QStringLiteral("splitter")) { + splitter->addWidget(restoreSplitterState(itemValue)); + } else if (itemType == QStringLiteral("area")) { + ToolWindowManagerArea* area = createArea(); + area->restoreState(itemValue); + splitter->addWidget(area); + } else { + qWarning("unknown item type"); + } + } + splitter->restoreState(QByteArray::fromBase64(savedData[QStringLiteral("state")].toByteArray())); + return splitter; +} + +void ToolWindowManager::updateDragPosition() { + if (!dragInProgress()) { return; } + if (!(qApp->mouseButtons() & Qt::LeftButton)) { + finishDrag(); + return; + } + + QPoint pos = QCursor::pos(); + m_hoverArea = NULL; + ToolWindowManagerWrapper* hoverWrapper = NULL; + + foreach(ToolWindowManagerArea* area, m_areas) { + // don't allow dragging a whole wrapper into a subset of itself + if (m_draggedWrapper && area->window() == m_draggedWrapper->window()) { + continue; + } + if (area->rect().contains(area->mapFromGlobal(pos))) { + m_hoverArea = area; + break; + } + } + + if (m_hoverArea == NULL) { + foreach(ToolWindowManagerWrapper* wrapper, m_wrappers) { + // don't allow dragging a whole wrapper into a subset of itself + if (wrapper == m_draggedWrapper) { + continue; + } + if (wrapper->rect().contains(wrapper->mapFromGlobal(pos))) { + hoverWrapper = wrapper; + break; + } + } + + // if we found a wrapper and it's not empty, then we fill into a gap between two areas in a + // splitter. Search down the hierarchy until we find a splitter whose handle intersects the + // cursor and pick an area to map to. + if (hoverWrapper) { + QSplitter* splitter = qobject_cast(hoverWrapper->layout()->itemAt(0)->widget()); + + while (splitter) { + QSplitter* previous = splitter; + + for (int h=1; h < splitter->count(); h++) { + QSplitterHandle* handle = splitter->handle(h); + + if (handle->rect().contains(handle->mapFromGlobal(pos))) { + QWidget* a = splitter->widget(h); + QWidget* b = splitter->widget(h+1); + + // try the first widget, if it's an area stop + m_hoverArea = qobject_cast(a); + if (m_hoverArea) + break; + + // then the second widget + m_hoverArea = qobject_cast(b); + if (m_hoverArea) + break; + + // neither widget is an area - let's search for a splitter to recurse to + splitter = qobject_cast(a); + if (splitter) + break; + + splitter = qobject_cast(b); + if (splitter) + break; + + // neither side is an area or a splitter - should be impossible, but stop recursing + // and treat this like a floating window + qWarning("Couldn't find splitter or area at terminal side of splitter"); + splitter = NULL; + hoverWrapper = NULL; + break; + } + } + + // if we still have a splitter, and didn't find an area, find which widget contains the + // cursor and recurse to that splitter + if (previous == splitter && !m_hoverArea) + { + for (int w=0; w < splitter->count(); w++) { + QWidget* widget = splitter->widget(w); + + if (widget->rect().contains(widget->mapFromGlobal(pos))) { + splitter = qobject_cast(widget); + if (splitter) + break; + + // if this isn't a splitter, and it's not an area (since that would have been found + // before any of this started) then bail out + qWarning("cursor inside unknown child widget that isn't a splitter or area"); + splitter = NULL; + hoverWrapper = NULL; + break; + } + } + } + + // we found an area to use! stop now + if (m_hoverArea) + break; + + // if we still haven't found anything, bail out + if (previous == splitter) + { + qWarning("Couldn't find cursor inside any child of wrapper"); + splitter = NULL; + hoverWrapper = NULL; + break; + } + } + } + } + + if (m_hoverArea || hoverWrapper) { + ToolWindowManagerWrapper* wrapper = hoverWrapper; + if (m_hoverArea) + wrapper = findClosestParent(m_hoverArea); + QRect wrapperGeometry; + wrapperGeometry.setSize(wrapper->rect().size()); + wrapperGeometry.moveTo(wrapper->mapToGlobal(QPoint(0,0))); + + const int margin = m_dropHotspotMargin; + + const int size = m_dropHotspotDimension; + const int hsize = size / 2; + + if (m_hoverArea) { + QRect areaClientRect; + + // calculate the rect of the area + areaClientRect.setTopLeft(m_hoverArea->mapToGlobal(QPoint(0,0))); + areaClientRect.setSize(m_hoverArea->rect().size()); + + // subtract the rect for the tab bar. + areaClientRect.adjust(0, m_hoverArea->tabBar()->rect().height(), 0, 0); + + QPoint c = areaClientRect.center(); + + m_dropHotspots[AddTo]->move(c + QPoint(-hsize, -hsize)); + m_dropHotspots[AddTo]->show(); + + m_dropHotspots[TopOf]->move(c + QPoint(-hsize, -hsize-margin-size)); + m_dropHotspots[TopOf]->show(); + + m_dropHotspots[LeftOf]->move(c + QPoint(-hsize-margin-size, -hsize)); + m_dropHotspots[LeftOf]->show(); + + m_dropHotspots[RightOf]->move(c + QPoint( hsize+margin, -hsize)); + m_dropHotspots[RightOf]->show(); + + m_dropHotspots[BottomOf]->move(c + QPoint(-hsize, hsize+margin)); + m_dropHotspots[BottomOf]->show(); + + c = wrapperGeometry.center(); + + m_dropHotspots[TopWindowSide]->move(QPoint(c.x() - hsize, wrapperGeometry.y() + margin * 2)); + m_dropHotspots[TopWindowSide]->show(); + + m_dropHotspots[LeftWindowSide]->move(QPoint(wrapperGeometry.x() + margin * 2, c.y() - hsize)); + m_dropHotspots[LeftWindowSide]->show(); + + m_dropHotspots[RightWindowSide]->move(QPoint(wrapperGeometry.right() - size - margin * 2, c.y() - hsize)); + m_dropHotspots[RightWindowSide]->show(); + + m_dropHotspots[BottomWindowSide]->move(QPoint(c.x() - hsize, wrapperGeometry.bottom() - size - margin * 2)); + m_dropHotspots[BottomWindowSide]->show(); + } else { + m_dropHotspots[AddTo]->move(wrapperGeometry.center() + QPoint(-hsize, -hsize)); + m_dropHotspots[AddTo]->show(); + + m_dropHotspots[TopOf]->hide(); + m_dropHotspots[LeftOf]->hide(); + m_dropHotspots[RightOf]->hide(); + m_dropHotspots[BottomOf]->hide(); + + m_dropHotspots[TopWindowSide]->hide(); + m_dropHotspots[LeftWindowSide]->hide(); + m_dropHotspots[RightWindowSide]->hide(); + m_dropHotspots[BottomWindowSide]->hide(); + } + + for(QWidget *hotspot : m_dropHotspots) + if(hotspot) + hotspot->show(); + } else { + for(QWidget *hotspot : m_dropHotspots) + if(hotspot) + hotspot->hide(); + } + + AreaReferenceType hotspot = currentHotspot(); + if ((m_hoverArea || hoverWrapper) && + (hotspot == AddTo || + hotspot == LeftOf || hotspot == RightOf || + hotspot == TopOf || hotspot == BottomOf)) { + QWidget *parent = m_hoverArea; + if(parent == NULL) + parent = hoverWrapper; + + QRect g = parent->geometry(); + g.moveTopLeft(parent->parentWidget()->mapToGlobal(g.topLeft())); + + if (hotspot == LeftOf) + g.adjust(0, 0, -g.width()/2, 0); + else if (hotspot == RightOf) + g.adjust(g.width()/2, 0, 0, 0); + else if (hotspot == TopOf) + g.adjust(0, 0, 0, -g.height()/2); + else if (hotspot == BottomOf) + g.adjust(0, g.height()/2, 0, 0); + + QRect tabGeom; + + if (hotspot == AddTo && m_hoverArea && m_hoverArea->count() > 1) { + QTabBar* tb = m_hoverArea->tabBar(); + g.adjust(0, tb->rect().height(), 0, 0); + + int idx = tb->tabAt(tb->mapFromGlobal(pos)); + + if (idx == -1) { + tabGeom = tb->tabRect(m_hoverArea->count()-1); + tabGeom.moveTo(tb->mapToGlobal(QPoint(0,0)) + tabGeom.topLeft()); + + // move the tab one to the right, to indicate the tab is being added after the last one. + tabGeom.moveLeft(tabGeom.left() + tabGeom.width()); + + // clamp from the right, to ensure we don't display any tab off the end of the range + if(tabGeom.right() > g.right()) + tabGeom.moveLeft(g.right() - tabGeom.width()); + } else { + tabGeom = tb->tabRect(idx); + tabGeom.moveTo(tb->mapToGlobal(QPoint(0,0)) + tabGeom.topLeft()); + } + } + + m_previewOverlay->setGeometry(g); + + m_previewTabOverlay->setGeometry(tabGeom); + } else if((m_hoverArea || hoverWrapper) && + (hotspot == LeftWindowSide || hotspot == RightWindowSide || + hotspot == TopWindowSide || hotspot == BottomWindowSide)) { + ToolWindowManagerWrapper* wrapper = hoverWrapper; + if (m_hoverArea) + wrapper = findClosestParent(m_hoverArea); + + QRect g; + g.moveTopLeft(wrapper->mapToGlobal(QPoint())); + g.setSize(wrapper->rect().size()); + + if(hotspot == LeftWindowSide) + g.adjust(0, 0, -(g.width()*5)/6, 0); + else if(hotspot == RightWindowSide) + g.adjust((g.width()*5)/6, 0, 0, 0); + else if(hotspot == TopWindowSide) + g.adjust(0, 0, 0, -(g.height()*3)/4); + else if(hotspot == BottomWindowSide) + g.adjust(0, (g.height()*3)/4, 0, 0); + + m_previewOverlay->setGeometry(g); + m_previewTabOverlay->setGeometry(QRect()); + } else { + bool allowFloat = m_allowFloatingWindow; + + for (QWidget *w : m_draggedToolWindows) + allowFloat &= !(toolWindowProperties(w) & DisallowFloatWindow); + + // no hotspot highlighted, draw geometry for a float window if previewing a tear-off, or draw + // nothing if we're dragging a float window as it moves itself. + // we also don't render any preview tear-off when floating windows are disallowed + if (m_draggedWrapper || !allowFloat) { + m_previewOverlay->setGeometry(QRect()); + } else { + QRect r; + for (QWidget *w : m_draggedToolWindows) { + if (w->isVisible()) + r = r.united(w->rect()); + } + m_previewOverlay->setGeometry(pos.x(), pos.y(), r.width(), r.height()); + } + m_previewTabOverlay->setGeometry(QRect()); + } + + m_previewOverlay->show(); + m_previewTabOverlay->show(); + for(QWidget *h : m_dropHotspots) + if(h && h->isVisible()) + h->raise(); +} + +void ToolWindowManager::abortDrag() { + if (!dragInProgress()) + return; + + m_previewOverlay->hide(); + m_previewTabOverlay->hide(); + for(QWidget *hotspot : m_dropHotspots) + if(hotspot) + hotspot->hide(); + m_draggedToolWindows.clear(); + m_draggedWrapper = NULL; + qApp->removeEventFilter(this); +} + +void ToolWindowManager::finishDrag() { + if (!dragInProgress()) { + qWarning("unexpected finishDrag"); + return; + } + qApp->removeEventFilter(this); + + // move these locally to prevent re-entrancy + QList draggedToolWindows = m_draggedToolWindows; + ToolWindowManagerWrapper* draggedWrapper = m_draggedWrapper; + + m_draggedToolWindows.clear(); + m_draggedWrapper = NULL; + + AreaReferenceType hotspot = currentHotspot(); + + m_previewOverlay->hide(); + m_previewTabOverlay->hide(); + for(QWidget *h : m_dropHotspots) + if(h) + h->hide(); + + if (hotspot == NewFloatingArea) { + // check if we're dragging a whole float window, if so we don't do anything as it's already moved + if (!draggedWrapper) { + bool allowFloat = m_allowFloatingWindow; + + for (QWidget *w : draggedToolWindows) + allowFloat &= !(toolWindowProperties(w) & DisallowFloatWindow); + + if (allowFloat) + { + QRect r; + for (QWidget *w : draggedToolWindows) { + if (w->isVisible()) + r = r.united(w->rect()); + } + + moveToolWindows(draggedToolWindows, NewFloatingArea); + + ToolWindowManagerArea *area = areaOf(draggedToolWindows[0]); + + area->parentWidget()->resize(r.size()); + } + } + } else { + if (m_hoverArea) { + AreaReference ref(hotspot, m_hoverArea); + ref.dragResult = true; + moveToolWindows(draggedToolWindows, ref); + } else { + moveToolWindows(draggedToolWindows, AreaReference(EmptySpace)); + } + } +} + +void ToolWindowManager::drawHotspotPixmaps() { + for (AreaReferenceType ref : { AddTo, LeftOf, TopOf, RightOf, BottomOf }) { + m_pixmaps[ref] = QPixmap(m_dropHotspotDimension*devicePixelRatio(), m_dropHotspotDimension*devicePixelRatio()); + m_pixmaps[ref].setDevicePixelRatio(devicePixelRatioF()); + + QPainter p(&m_pixmaps[ref]); + p.setCompositionMode(QPainter::CompositionMode_Source); + p.setRenderHint(QPainter::Antialiasing); + p.setRenderHint(QPainter::HighQualityAntialiasing); + + QRectF rect(0, 0, m_dropHotspotDimension, m_dropHotspotDimension); + + p.fillRect(rect, Qt::transparent); + + rect = rect.marginsAdded(QMarginsF(-1, -1, -1, -1)); + + p.setPen(QPen(QBrush(Qt::darkGray), 1.5)); + p.setBrush(QBrush(Qt::lightGray)); + p.drawRoundedRect(rect, 1.5, 1.5, Qt::AbsoluteSize); + + rect = rect.marginsAdded(QMarginsF(-4, -4, -4, -4)); + + QRectF fullRect = rect; + + if (ref == LeftOf) + rect = rect.marginsAdded(QMarginsF(0, 0, -12, 0)); + else if (ref == TopOf) + rect = rect.marginsAdded(QMarginsF(0, 0, 0, -12)); + else if (ref == RightOf) + rect = rect.marginsAdded(QMarginsF(-12, 0, 0, 0)); + else if (ref == BottomOf) + rect = rect.marginsAdded(QMarginsF(0, -12, 0, 0)); + + p.setPen(QPen(QBrush(Qt::black), 1.0)); + p.setBrush(QBrush(Qt::white)); + p.drawRect(rect); + + // add a little title bar + rect.setHeight(3); + p.fillRect(rect, Qt::SolidPattern); + + // for the sides, add an arrow. + if (ref != AddTo) { + QPainterPath path; + + if (ref == LeftOf) { + QPointF tip = fullRect.center() + QPointF( 4, 0); + + path.addPolygon(QPolygonF({tip, + tip + QPoint( 3, 3), + tip + QPoint( 3, -3), + })); + } else if (ref == TopOf) { + QPointF tip = fullRect.center() + QPointF( 0, 4); + + path.addPolygon(QPolygonF({tip, + tip + QPointF(-3, 3), + tip + QPointF( 3, 3), + })); + } else if (ref == RightOf) { + QPointF tip = fullRect.center() + QPointF(-4, 0); + + path.addPolygon(QPolygonF({tip, + tip + QPointF(-3, 3), + tip + QPointF(-3, -3), + })); + } else if (ref == BottomOf) { + QPointF tip = fullRect.center() + QPointF( 0, -4); + + path.addPolygon(QPolygonF({tip, + tip + QPointF(-3, -3), + tip + QPointF( 3, -3), + })); + } + + p.fillPath(path, QBrush(Qt::black)); + } + } + + // duplicate these pixmaps by default + m_pixmaps[LeftWindowSide] = m_pixmaps[LeftOf]; + m_pixmaps[RightWindowSide] = m_pixmaps[RightOf]; + m_pixmaps[TopWindowSide] = m_pixmaps[TopOf]; + m_pixmaps[BottomWindowSide] = m_pixmaps[BottomOf]; +} + +ToolWindowManager::AreaReferenceType ToolWindowManager::currentHotspot() { + QPoint pos = QCursor::pos(); + + for (int i=0; i < NumReferenceTypes; i++) { + if (m_dropHotspots[i] && m_dropHotspots[i]->isVisible() && + m_dropHotspots[i]->geometry().contains(pos)) { + return (ToolWindowManager::AreaReferenceType)i; + } + } + + if (m_hoverArea) { + QTabBar* tb = m_hoverArea->tabBar(); + if (tb->rect().contains(tb->mapFromGlobal(QCursor::pos()))) + return AddTo; + } + + return NewFloatingArea; +} + +bool ToolWindowManager::eventFilter(QObject *object, QEvent *event) { + if (event->type() == QEvent::MouseButtonRelease) { + // right clicking aborts any drag in progress + if (static_cast(event)->button() == Qt::RightButton) + abortDrag(); + } else if (event->type() == QEvent::KeyPress) { + // pressing escape any drag in progress + QKeyEvent *ke = (QKeyEvent *)event; + if(ke->key() == Qt::Key_Escape) { + abortDrag(); + } + } + return QWidget::eventFilter(object, event); +} + +bool ToolWindowManager::allowClose(QWidget *toolWindow) { + if (!m_toolWindows.contains(toolWindow)) { + qWarning("unknown tool window"); + return true; + } + int methodIndex = toolWindow->metaObject()->indexOfMethod(QMetaObject::normalizedSignature("checkAllowClose()")); + + if(methodIndex >= 0) { + bool ret = true; + toolWindow->metaObject()->method(methodIndex).invoke(toolWindow, Qt::DirectConnection, Q_RETURN_ARG(bool, ret)); + + return ret; + } + + return true; +} + +void ToolWindowManager::tabCloseRequested(int index) { + ToolWindowManagerArea* tabWidget = qobject_cast(sender()); + if (!tabWidget) { + qWarning("sender is not a ToolWindowManagerArea"); + return; + } + QWidget* toolWindow = tabWidget->widget(index); + if (!m_toolWindows.contains(toolWindow)) { + qWarning("unknown tab in tab widget"); + return; + } + + if (!allowClose(toolWindow)) + return; + + if(toolWindowProperties(toolWindow) & ToolWindowManager::HideOnClose) + hideToolWindow(toolWindow); + else + removeToolWindow(toolWindow); +} + +void ToolWindowManager::windowTitleChanged(const QString &) { + QWidget* toolWindow = qobject_cast(sender()); + if(!toolWindow) { + return; + } + ToolWindowManagerArea *area = areaOf(toolWindow); + if(area) { + area->updateToolWindow(toolWindow); + } +} + +QSplitter *ToolWindowManager::createSplitter() { + QSplitter* splitter = new ToolWindowManagerSplitter(); + splitter->setChildrenCollapsible(false); + return splitter; +} + +ToolWindowManager::AreaReference::AreaReference(ToolWindowManager::AreaReferenceType type, ToolWindowManagerArea *area, float percentage) { + m_type = type; + m_percentage = percentage; + dragResult = false; + setWidget(area); +} + +void ToolWindowManager::AreaReference::setWidget(QWidget *widget) { + if (m_type == LastUsedArea || m_type == NewFloatingArea || m_type == NoArea || m_type == EmptySpace) { + if (widget != 0) { + qWarning("area parameter ignored for this type"); + } + m_widget = 0; + } else if (m_type == AddTo) { + m_widget = qobject_cast(widget); + if (!m_widget) { + qWarning("only ToolWindowManagerArea can be used with this type"); + } + } else { + if (!qobject_cast(widget) && + !qobject_cast(widget)) { + qWarning("only ToolWindowManagerArea or splitter can be used with this type"); + m_widget = 0; + } else { + m_widget = widget; + } + } +} + +ToolWindowManagerArea *ToolWindowManager::AreaReference::area() const { + return qobject_cast(m_widget); +} + +ToolWindowManager::AreaReference::AreaReference(ToolWindowManager::AreaReferenceType type, QWidget *widget) { + m_type = type; + dragResult = false; + setWidget(widget); +} diff --git a/3rdparty/ToolWindowManager/src/ToolWindowManagerArea.cpp b/3rdparty/ToolWindowManager/src/ToolWindowManagerArea.cpp new file mode 100644 index 0000000..5b3afec --- /dev/null +++ b/3rdparty/ToolWindowManager/src/ToolWindowManagerArea.cpp @@ -0,0 +1,338 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2014 Pavel Strakhov + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ +#include "ToolWindowManagerArea.h" +#include "ToolWindowManagerTabBar.h" +#include "ToolWindowManagerWrapper.h" +#include "ToolWindowManager.h" +#include +#include +#include + +static void showCloseButton(QTabBar *bar, int index, bool show) { + QWidget *button = bar->tabButton(index, QTabBar::RightSide); + if(button == NULL) + button = bar->tabButton(index, QTabBar::LeftSide); + + if(button) + button->resize(show ? QSize(16, 16) : QSize(0, 0)); +} + +ToolWindowManagerArea::ToolWindowManagerArea(ToolWindowManager *manager, QWidget *parent) : + QTabWidget(parent) +, m_manager(manager) +{ + m_tabBar = new ToolWindowManagerTabBar(this); + setTabBar(m_tabBar); + + m_tabBar->setTabsClosable(true); + + m_dragCanStart = false; + m_tabDragCanStart = false; + m_inTabMoved = false; + m_userCanDrop = true; + setMovable(true); + setDocumentMode(true); + tabBar()->installEventFilter(this); + m_manager->m_areas << this; + + QObject::connect(tabBar(), &QTabBar::tabMoved, this, &ToolWindowManagerArea::tabMoved); + QObject::connect(tabBar(), &QTabBar::tabCloseRequested, this, &ToolWindowManagerArea::tabClosing); + QObject::connect(tabBar(), &QTabBar::tabCloseRequested, this, &QTabWidget::tabCloseRequested); + QObject::connect(this, &QTabWidget::currentChanged, this, &ToolWindowManagerArea::tabSelected); +} + +ToolWindowManagerArea::~ToolWindowManagerArea() { + m_manager->m_areas.removeOne(this); +} + +void ToolWindowManagerArea::addToolWindow(QWidget *toolWindow, int insertIndex) { + addToolWindows(QList() << toolWindow, insertIndex); +} + +void ToolWindowManagerArea::addToolWindows(const QList &toolWindows, int insertIndex) { + int index = 0; + foreach(QWidget* toolWindow, toolWindows) { + index = insertTab(insertIndex, toolWindow, toolWindow->windowIcon(), toolWindow->windowTitle()); + insertIndex = index+1; + } + setCurrentIndex(index); + for (int i=0; i < count(); i++) { + updateToolWindow(widget(i)); + } + m_manager->m_lastUsedArea = this; +} + +QList ToolWindowManagerArea::toolWindows() { + QList result; + for(int i = 0; i < count(); i++) { + result << widget(i); + } + return result; +} + +void ToolWindowManagerArea::updateToolWindow(QWidget* toolWindow) { + int index = indexOf(toolWindow); + if(index >= 0) { + if(m_manager->toolWindowProperties(toolWindow) & ToolWindowManager::HideCloseButton) { + showCloseButton(tabBar(), index, false); + } else { + showCloseButton(tabBar(), index, true); + } + tabBar()->setTabText(index, toolWindow->windowTitle()); + } +} + +void ToolWindowManagerArea::mouseMoveEvent(QMouseEvent *) { + check_mouse_move(); +} + +bool ToolWindowManagerArea::eventFilter(QObject *object, QEvent *event) { + if (object == tabBar()) { + if (event->type() == QEvent::MouseButtonPress && + qApp->mouseButtons() == Qt::LeftButton) { + + QPoint pos = static_cast(event)->pos(); + + int tabIndex = tabBar()->tabAt(pos); + + // can start tab drag only if mouse is at some tab, not at empty tabbar space + if (tabIndex >= 0) { + m_tabDragCanStart = true; + + if (m_manager->toolWindowProperties(widget(tabIndex)) & ToolWindowManager::DisableDraggableTab) { + setMovable(false); + } else { + setMovable(true); + } + } else if (m_tabBar == NULL || !m_tabBar->inButton(pos)) { + m_dragCanStart = true; + m_dragCanStartPos = QCursor::pos(); + } + } else if (event->type() == QEvent::MouseButtonPress && + qApp->mouseButtons() == Qt::MiddleButton) { + + int tabIndex = tabBar()->tabAt(static_cast(event)->pos()); + + if(tabIndex >= 0) { + QWidget *w = widget(tabIndex); + + if(!(m_manager->toolWindowProperties(w) & ToolWindowManager::HideCloseButton)) { + m_manager->removeToolWindow(w); + } + } + } else if (event->type() == QEvent::MouseButtonRelease) { + m_tabDragCanStart = false; + m_dragCanStart = false; + m_manager->updateDragPosition(); + } else if (event->type() == QEvent::MouseMove) { + m_manager->updateDragPosition(); + if (m_tabDragCanStart) { + if (tabBar()->rect().contains(static_cast(event)->pos())) { + return false; + } + if (qApp->mouseButtons() != Qt::LeftButton) { + return false; + } + QWidget* toolWindow = currentWidget(); + if (!toolWindow || !m_manager->m_toolWindows.contains(toolWindow)) { + return false; + } + m_tabDragCanStart = false; + //stop internal tab drag in QTabBar + QMouseEvent* releaseEvent = new QMouseEvent(QEvent::MouseButtonRelease, + static_cast(event)->pos(), + Qt::LeftButton, Qt::LeftButton, 0); + qApp->sendEvent(tabBar(), releaseEvent); + m_manager->startDrag(QList() << toolWindow, NULL); + } else if (m_dragCanStart) { + check_mouse_move(); + } + } + } + return QTabWidget::eventFilter(object, event); +} + +void ToolWindowManagerArea::tabInserted(int index) { + // update the select order. Increment any existing index after the insertion point to keep the + // indices in the list up to date. + for (int &idx : m_tabSelectOrder) { + if (idx >= index) + idx++; + } + + // if the tab inserted is the current index (most likely) then add it at the end, otherwise + // add it next-to-end (to keep the most recent tab the same). + if (currentIndex() == index || m_tabSelectOrder.isEmpty()) + m_tabSelectOrder.append(index); + else + m_tabSelectOrder.insert(m_tabSelectOrder.count()-1, index); + + QTabWidget::tabInserted(index); +} + +void ToolWindowManagerArea::tabRemoved(int index) { + // update the select order. Remove the index that just got deleted, and decrement any index + // greater than it to remap to their new indices + m_tabSelectOrder.removeOne(index); + + for (int &idx : m_tabSelectOrder) { + if (idx > index) + idx--; + } + + QTabWidget::tabRemoved(index); +} + +void ToolWindowManagerArea::tabSelected(int index) { + // move this tab to the end of the select order, as long as we have it - if it's a new index then + // ignore and leave it to be handled in tabInserted() + if (m_tabSelectOrder.contains(index)) { + m_tabSelectOrder.removeOne(index); + m_tabSelectOrder.append(index); + } + + ToolWindowManagerWrapper* wrapper = m_manager->wrapperOf(this); + if (wrapper) + wrapper->updateTitle(); +} + +void ToolWindowManagerArea::tabClosing(int index) { + // before closing this index, switch the current index to the next tab in succession. + + // should never get here but let's check this + if (m_tabSelectOrder.isEmpty()) + return; + + // when closing the last tab there's nothing to do + if (m_tabSelectOrder.count() == 1) + return; + + // if the last in the select order is being closed, switch to the next most selected tab + if (m_tabSelectOrder.last() == index) + setCurrentIndex(m_tabSelectOrder.at(m_tabSelectOrder.count()-2)); +} + +QVariantMap ToolWindowManagerArea::saveState() { + QVariantMap result; + result[QStringLiteral("type")] = QStringLiteral("area"); + result[QStringLiteral("currentIndex")] = currentIndex(); + QVariantList objects; + objects.reserve(count()); + for(int i = 0; i < count(); i++) { + QWidget *w = widget(i); + QString name = w->objectName(); + if (name.isEmpty()) { + qWarning("cannot save state of tool window without object name"); + } else { + QVariantMap objectData; + objectData[QStringLiteral("name")] = name; + objectData[QStringLiteral("data")] = w->property("persistData"); + objects.push_back(objectData); + } + } + result[QStringLiteral("objects")] = objects; + return result; +} + +void ToolWindowManagerArea::restoreState(const QVariantMap &savedData) { + for(QVariant object : savedData[QStringLiteral("objects")].toList()) { + QVariantMap objectData = object.toMap(); + if (objectData.isEmpty()) { continue; } + QString objectName = objectData[QStringLiteral("name")].toString(); + if (objectName.isEmpty()) { continue; } + QWidget *t = NULL; + for(QWidget* toolWindow : m_manager->m_toolWindows) { + if (toolWindow->objectName() == objectName) { + t = toolWindow; + break; + } + } + if (t == NULL) t = m_manager->createToolWindow(objectName); + if (t) { + t->setProperty("persistData", objectData[QStringLiteral("data")]); + addToolWindow(t); + } else { + qWarning("tool window with name '%s' not found or created", objectName.toLocal8Bit().constData()); + } + } + setCurrentIndex(savedData[QStringLiteral("currentIndex")].toInt()); +} + +void ToolWindowManagerArea::check_mouse_move() { + if (qApp->mouseButtons() != Qt::LeftButton && m_dragCanStart) { + m_dragCanStart = false; + } + m_manager->updateDragPosition(); + if (m_dragCanStart && + (QCursor::pos() - m_dragCanStartPos).manhattanLength() > 10) { + m_dragCanStart = false; + QList toolWindows; + for(int i = 0; i < count(); i++) { + QWidget* toolWindow = widget(i); + if (!m_manager->m_toolWindows.contains(toolWindow)) { + qWarning("tab widget contains unmanaged widget"); + } else { + toolWindows << toolWindow; + } + } + m_manager->startDrag(toolWindows, NULL); + } +} + +bool ToolWindowManagerArea::useMinimalTabBar() { + QWidget *w = widget(0); + if (w == NULL) + return false; + + return (m_manager->toolWindowProperties(w) & ToolWindowManager::AlwaysDisplayFullTabs) == 0; +} + +void ToolWindowManagerArea::tabMoved(int from, int to) { + if(m_inTabMoved) return; + + // update the select order. + // This amounts to just a swap - any indices other than the pair in question are unaffected since + // one tab is removed (above/below) and added (below/above) so the indices themselves remain the + // same. + for (int &idx : m_tabSelectOrder) { + if (idx == from) + idx = to; + else if (idx == to) + idx = from; + } + + QWidget *a = widget(from); + QWidget *b = widget(to); + + if(!a || !b) return; + + if(m_manager->toolWindowProperties(a) & ToolWindowManager::DisableDraggableTab || + m_manager->toolWindowProperties(b) & ToolWindowManager::DisableDraggableTab) + { + m_inTabMoved = true; + tabBar()->moveTab(to, from); + m_inTabMoved = false; + } +} diff --git a/3rdparty/ToolWindowManager/src/ToolWindowManagerSplitter.cpp b/3rdparty/ToolWindowManager/src/ToolWindowManagerSplitter.cpp new file mode 100644 index 0000000..7b5e0ab --- /dev/null +++ b/3rdparty/ToolWindowManager/src/ToolWindowManagerSplitter.cpp @@ -0,0 +1,72 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017 Baldur Karlsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ +#include "ToolWindowManagerSplitter.h" +#include +#include + +ToolWindowManagerSplitter::ToolWindowManagerSplitter(QWidget *parent) : + QSplitter(parent) +{ +} + +ToolWindowManagerSplitter::~ToolWindowManagerSplitter() { +} + +void ToolWindowManagerSplitter::childEvent(QChildEvent *event) { + QList s = sizes(); + + QWidget *w = qobject_cast(event->child()); + int idx = -1; + if (w) + idx = indexOf(w); + + QSplitter::childEvent(event); + + if (event->type() == QEvent::ChildRemoved && idx >= 0 && idx < s.count()) { + int removedSize = s[idx]; + + s.removeAt(idx); + + // if we removed an item at one extreme or another, the new end should get all the space + // (unless the list is now empty) + if (idx == 0) { + if(!s.isEmpty()) + s[0] += removedSize; + } else if (idx == s.count()) { + if(!s.isEmpty()) + s[s.count()-1] += removedSize; + } else { + // we removed an item in the middle, share the space between its previous neighbours, now in + // [idx-1] and [idx], and we know they're valid since if there were only two elements before + // the removal one or the other case above would have matched. So there are at least two + // elements now and idx > 0 + + s[idx-1] += removedSize/2; + s[idx] += removedSize/2; + } + + setSizes(s); + } +} diff --git a/3rdparty/ToolWindowManager/src/ToolWindowManagerTabBar.cpp b/3rdparty/ToolWindowManager/src/ToolWindowManagerTabBar.cpp new file mode 100644 index 0000000..784dd79 --- /dev/null +++ b/3rdparty/ToolWindowManager/src/ToolWindowManagerTabBar.cpp @@ -0,0 +1,382 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017 Baldur Karlsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ +#include "ToolWindowManager.h" +#include "ToolWindowManagerTabBar.h" +#include "ToolWindowManagerArea.h" +#include "ToolWindowManagerWrapper.h" +#include +#include +#include +#include + +ToolWindowManagerTabBar::ToolWindowManagerTabBar(QWidget *parent) : + QTabBar(parent) +{ + m_tabsClosable = false; + + setMouseTracking(true); + + m_area = qobject_cast(parent); + + // Workaround for extremely dodgy KDE behaviour - by default the KDE theme will install event + // filters on various widgets such as QTabBar and any descendents, and if a click is detected on + // them that isn't on a tab it will immediately start moving the window, interfering with our own + // click-to-drag behaviour. + setProperty("_kde_no_window_grab", true); + + QStyleOptionToolButton buttonOpt; + + int size = style()->pixelMetric(QStyle::PM_SmallIconSize, 0, this); + + buttonOpt.initFrom(parentWidget()); + buttonOpt.iconSize = QSize(size, size); + buttonOpt.subControls = 0; + buttonOpt.activeSubControls = 0; + buttonOpt.features = QStyleOptionToolButton::None; + buttonOpt.arrowType = Qt::NoArrow; + buttonOpt.state |= QStyle::State_AutoRaise; + + // TODO make our own pin icon, that is pinned/unpinned + m_pin.icon = style()->standardIcon(QStyle::SP_TitleBarNormalButton, &buttonOpt, this); + m_close.icon = style()->standardIcon(QStyle::SP_TitleBarCloseButton, &buttonOpt, this); + + m_pin.hover = m_pin.clicked = false; + m_close.hover = m_close.clicked = false; +} + +ToolWindowManagerTabBar::~ToolWindowManagerTabBar() { +} + +bool ToolWindowManagerTabBar::useMinimalBar() const +{ + return false; +} + +QSize ToolWindowManagerTabBar::sizeHint() const { + if(useMinimalBar()) { + if (floatingWindowChild()) + return QSize(0, 0); + + QFontMetrics fm = fontMetrics(); + + int iconSize = style()->pixelMetric(QStyle::PM_SmallIconSize, 0, this); + int mw = style()->pixelMetric(QStyle::PM_DockWidgetTitleMargin, 0, this); + + int h = qMax(fm.height(), iconSize) + 2*mw; + + return QSize(m_area->width(), h); + } + + return QTabBar::sizeHint(); +} + +QSize ToolWindowManagerTabBar::minimumSizeHint() const { + if (useMinimalBar()) { + if (floatingWindowChild()) + return QSize(0, 0); + + QFontMetrics fm = fontMetrics(); + + int iconSize = style()->pixelMetric(QStyle::PM_SmallIconSize, 0, this); + int mw = style()->pixelMetric(QStyle::PM_DockWidgetTitleMargin, 0, this); + + int h = qMax(fm.height(), iconSize) + 2*mw; + + return QSize(h, h); + } + + return QTabBar::minimumSizeHint(); +} + +bool ToolWindowManagerTabBar::inButton(QPoint pos) { + return m_pin.rect.contains(pos) || m_close.rect.contains(pos); +} + +void ToolWindowManagerTabBar::paintEvent(QPaintEvent *event) { + if (useMinimalBar()) { + if (floatingWindowChild()) + return; + + QStylePainter p(this); + + QStyleOptionDockWidget option; + + option.initFrom(parentWidget()); + option.rect = m_titleRect; + option.title = tabText(0); + option.closable = m_tabsClosable; + option.movable = false; + // we only set floatable true so we can hijack the float button for our own pin/auto-hide button + option.floatable = true; + + Shape s = shape(); + option.verticalTitleBar = s == RoundedEast || s == TriangularEast || + s == RoundedWest || s == TriangularWest; + + p.drawControl(QStyle::CE_DockWidgetTitle, option); + + int size = style()->pixelMetric(QStyle::PM_SmallIconSize, 0, this); + + QStyleOptionToolButton buttonOpt; + + buttonOpt.initFrom(parentWidget()); + buttonOpt.iconSize = QSize(size, size); + buttonOpt.subControls = 0; + buttonOpt.activeSubControls = 0; + buttonOpt.features = QStyleOptionToolButton::None; + buttonOpt.arrowType = Qt::NoArrow; + buttonOpt.state = QStyle::State_Active|QStyle::State_Enabled|QStyle::State_AutoRaise; + + buttonOpt.rect = m_pin.rect; + buttonOpt.icon = m_pin.icon; + + ToolWindowManager::ToolWindowProperty props = + m_area->m_manager->toolWindowProperties(m_area->widget(0)); + + bool tabClosable = (props & ToolWindowManager::HideCloseButton) == 0; + + if (!tabClosable && !m_pin.rect.isEmpty()) + buttonOpt.rect = m_close.rect; + + QStyle::State prevState = buttonOpt.state; + + if(m_pin.clicked) + buttonOpt.state |= QStyle::State_Sunken; + else if(m_pin.hover) + buttonOpt.state |= QStyle::State_Raised | QStyle::State_MouseOver; + + if (style()->styleHint(QStyle::SH_DockWidget_ButtonsHaveFrame, 0, this)) { + style()->drawPrimitive(QStyle::PE_PanelButtonTool, &buttonOpt, &p, this); + } + + style()->drawComplexControl(QStyle::CC_ToolButton, &buttonOpt, &p, this); + + if (m_tabsClosable && tabClosable) { + buttonOpt.rect = m_close.rect; + buttonOpt.icon = m_close.icon; + + buttonOpt.state = prevState; + + if(m_close.clicked) + buttonOpt.state |= QStyle::State_Sunken; + else if(m_close.hover) + buttonOpt.state |= QStyle::State_Raised | QStyle::State_MouseOver; + + style()->drawPrimitive(QStyle::PE_IndicatorTabClose, &buttonOpt, &p, this); + } + return; + } + + QTabBar::paintEvent(event); +} + +void ToolWindowManagerTabBar::resizeEvent(QResizeEvent *event) { + QTabBar::resizeEvent(event); + + if (count() > 1 || floatingWindowChild()) + return; + + m_titleRect = QRect(0, 0, size().width(), sizeHint().height()); + + QStyleOptionDockWidget option; + + option.initFrom(parentWidget()); + option.rect = m_titleRect; + option.closable = m_tabsClosable; + option.movable = false; + // we only set floatable true so we can hijack the float button for our own pin/auto-hide button + option.floatable = true; + + m_pin.rect = style()->subElementRect(QStyle::SE_DockWidgetFloatButton, &option, this); + m_close.rect = style()->subElementRect(QStyle::SE_DockWidgetCloseButton, &option, this); + + // TODO - temporarily until this is implemented, hide the pin button. + m_pin.rect = QRect(); +} + +void ToolWindowManagerTabBar::mousePressEvent(QMouseEvent *event) { + QTabBar::mousePressEvent(event); + + if (count() > 1 || floatingWindowChild()) + return; + + ButtonData prevPin = m_pin; + ButtonData prevClose = m_close; + + ToolWindowManager::ToolWindowProperty props = + m_area->m_manager->toolWindowProperties(m_area->widget(0)); + + bool tabClosable = (props & ToolWindowManager::HideCloseButton) == 0; + + QRect pinRect = m_pin.rect; + QRect closeRect = m_close.rect; + + if (!tabClosable) { + if (!pinRect.isEmpty()) + pinRect = closeRect; + closeRect = QRect(); + } + + if (pinRect.contains(mapFromGlobal(QCursor::pos())) && + event->buttons() & Qt::LeftButton) { + m_pin.clicked = true; + } else { + m_pin.clicked = false; + } + + if (closeRect.contains(mapFromGlobal(QCursor::pos())) && + event->buttons() & Qt::LeftButton) { + m_close.clicked = true; + } else { + m_close.clicked = false; + } + + if (prevPin != m_pin || prevClose != m_close) + update(); + + event->accept(); +} + +void ToolWindowManagerTabBar::mouseMoveEvent(QMouseEvent *event) { + QTabBar::mouseMoveEvent(event); + + if (count() > 1 || floatingWindowChild()) + return; + + ButtonData prevPin = m_pin; + ButtonData prevClose = m_close; + + ToolWindowManager::ToolWindowProperty props = + m_area->m_manager->toolWindowProperties(m_area->widget(0)); + + bool tabClosable = (props & ToolWindowManager::HideCloseButton) == 0; + + QRect pinRect = m_pin.rect; + QRect closeRect = m_close.rect; + + if (!tabClosable) { + if (!pinRect.isEmpty()) + pinRect = closeRect; + closeRect = QRect(); + } + + if (pinRect.contains(mapFromGlobal(QCursor::pos()))) { + m_pin.hover = true; + if (event->buttons() & Qt::LeftButton) + m_pin.clicked = true; + } else { + m_pin.hover = false; + m_pin.clicked = false; + } + + if (closeRect.contains(mapFromGlobal(QCursor::pos()))) { + m_close.hover = true; + if (event->buttons() & Qt::LeftButton) + m_close.clicked = true; + } else { + m_close.hover = false; + m_close.clicked = false; + } + + if (prevPin != m_pin || prevClose != m_close) + update(); +} + +void ToolWindowManagerTabBar::leaveEvent(QEvent *) { + m_pin.hover = false; + m_pin.clicked = false; + + m_close.hover = false; + m_close.clicked = false; + + update(); +} + +void ToolWindowManagerTabBar::mouseReleaseEvent(QMouseEvent *event) { + QTabBar::mouseReleaseEvent(event); + + if (count() > 1 || floatingWindowChild()) + return; + + ToolWindowManager::ToolWindowProperty props = + m_area->m_manager->toolWindowProperties(m_area->widget(0)); + + bool tabClosable = (props & ToolWindowManager::HideCloseButton) == 0; + + QRect pinRect = m_pin.rect; + QRect closeRect = m_close.rect; + + if (!tabClosable) { + if (!pinRect.isEmpty()) + pinRect = closeRect; + closeRect = QRect(); + } + + if (pinRect.contains(mapFromGlobal(QCursor::pos()))) { + // process a pin of these tabs + + m_pin.clicked = false; + + update(); + + event->accept(); + } + + if (closeRect.contains(mapFromGlobal(QCursor::pos()))) { + if (m_area) + m_area->tabCloseRequested(0); + + m_close.clicked = false; + + update(); + + event->accept(); + } +} + +void ToolWindowManagerTabBar::tabInserted(int) { + updateClosable(); +} + +void ToolWindowManagerTabBar::tabRemoved(int) { + updateClosable(); +} + +void ToolWindowManagerTabBar::updateClosable() { + QTabBar::setTabsClosable(m_tabsClosable); +} + +bool ToolWindowManagerTabBar::floatingWindowChild() const { + ToolWindowManagerArea *area = qobject_cast(parentWidget()); + + if (area) { + ToolWindowManagerWrapper *wrapper = qobject_cast(area->parentWidget()); + + if (wrapper && wrapper->floating()) + return true; + } + + return false; +} diff --git a/3rdparty/ToolWindowManager/src/ToolWindowManagerWrapper.cpp b/3rdparty/ToolWindowManager/src/ToolWindowManagerWrapper.cpp new file mode 100644 index 0000000..c0ffbfd --- /dev/null +++ b/3rdparty/ToolWindowManager/src/ToolWindowManagerWrapper.cpp @@ -0,0 +1,478 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2014 Pavel Strakhov + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ +#include "ToolWindowManagerWrapper.h" +#include "ToolWindowManager.h" +#include "ToolWindowManagerArea.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +ToolWindowManagerWrapper::ToolWindowManagerWrapper(ToolWindowManager *manager, bool floating) : + QWidget(manager) +, m_manager(manager) +{ + Qt::WindowFlags flags = Qt::Window; + +#if defined(Q_OS_WIN32) + flags |= Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowCloseButtonHint; +#endif + + setMouseTracking(true); + + setWindowFlags(flags); + setWindowTitle(QStringLiteral(" ")); + + m_dragReady = false; + m_dragActive = false; + m_dragDirection = ResizeDirection::Count; + + m_floating = floating; + + QVBoxLayout* mainLayout = new QVBoxLayout(this); + mainLayout->setContentsMargins(0, 0, 0, 0); + mainLayout->setMargin(0); + mainLayout->setSpacing(0); + m_manager->m_wrappers << this; + + m_moveTimeout = new QTimer(this); + m_moveTimeout->setInterval(100); + m_moveTimeout->stop(); + QObject::connect(m_moveTimeout, &QTimer::timeout, this, &ToolWindowManagerWrapper::moveTimeout); + + m_closeButtonSize = 0; + m_frameWidth = 0; + m_titleHeight = 0; + + if (floating && (flags & Qt::FramelessWindowHint)) { + m_closeButtonSize = style()->pixelMetric(QStyle::PM_SmallIconSize, 0, this); + + QFontMetrics titleFontMetrics = fontMetrics(); + int mw = style()->pixelMetric(QStyle::PM_DockWidgetTitleMargin, 0, this); + + m_titleHeight = qMax(m_closeButtonSize + 2, titleFontMetrics.height() + 2*mw); + + m_frameWidth = style()->pixelMetric(QStyle::PM_DockWidgetFrameWidth, 0, this); + + mainLayout->setContentsMargins(QMargins(m_frameWidth+4, m_frameWidth+4 + m_titleHeight, + m_frameWidth+4, m_frameWidth+4)); + } + + if (floating) { + installEventFilter(this); + updateTitle(); + } +} + +ToolWindowManagerWrapper::~ToolWindowManagerWrapper() { + m_manager->m_wrappers.removeOne(this); +} + +void ToolWindowManagerWrapper::updateTitle() { + if (!m_floating) + return; + + // find the best candidate for a 'title' for this floating window. + if (layout()->count() > 0) { + QWidget *child = layout()->itemAt(0)->widget(); + + while (child) { + // if we've found an area, use its currently selected tab's text + if (ToolWindowManagerArea* area = qobject_cast(child)) { + setWindowTitle(area->tabText(area->currentIndex())); + return; + } + // otherwise we should have a splitter + if (QSplitter* splitter = qobject_cast(child)) { + // if it's empty, just bail + if (splitter->count() == 0) + break; + + // if it's vertical, we pick the first child and recurse + if (splitter->orientation() == Qt::Vertical) { + child = splitter->widget(0); + continue; + } + + // if it's horizontal there's ambiguity so we just pick the biggest one by size, with a + // tie-break for the leftmost one + QList sizes = splitter->sizes(); + int maxIdx = 0; + int maxSize = sizes[0]; + for (int i=1; i < sizes.count(); i++) { + if (sizes[i] > maxSize) { + maxSize = sizes[i]; + maxIdx = i; + } + } + + child = splitter->widget(maxIdx); + continue; + } + + // if not, use this object's window title + setWindowTitle(child->windowTitle()); + return; + } + } + + setWindowTitle(QStringLiteral("Tool Window")); +} + +void ToolWindowManagerWrapper::closeEvent(QCloseEvent *event) { + QList toolWindows; + foreach(ToolWindowManagerArea* tabWidget, findChildren()) { + if (ToolWindowManager::managerOf(tabWidget) == m_manager) { + toolWindows << tabWidget->toolWindows(); + } + } + + foreach(QWidget* toolWindow, toolWindows) { + if (!m_manager->allowClose(toolWindow)) { + event->ignore(); + return; + } + } + + foreach(QWidget* toolWindow, toolWindows) { + if(m_manager->toolWindowProperties(toolWindow) & ToolWindowManager::HideOnClose) + m_manager->hideToolWindow(toolWindow); + else + m_manager->removeToolWindow(toolWindow); + } +} + +bool ToolWindowManagerWrapper::eventFilter(QObject *object, QEvent *event) { + const Qt::CursorShape shapes[(int)ResizeDirection::Count] = { + Qt::SizeFDiagCursor, + Qt::SizeBDiagCursor, + Qt::SizeBDiagCursor, + Qt::SizeFDiagCursor, + Qt::SizeVerCursor, + Qt::SizeHorCursor, + Qt::SizeVerCursor, + Qt::SizeHorCursor, + }; + + if (object == this) { + if (event->type() == QEvent::MouseButtonRelease || + event->type() == QEvent::NonClientAreaMouseButtonRelease) { + m_dragReady = false; + m_dragDirection = ResizeDirection::Count; + if (!m_dragActive && m_closeRect.contains(mapFromGlobal(QCursor::pos()))) { + // catch clicks on the close button + close(); + } else { + // if the mouse button is released, let the manager finish the drag and don't call any more + // updates for any further move events + m_dragActive = false; + m_manager->updateDragPosition(); + } + } else if (event->type() == QEvent::MouseMove || + event->type() == QEvent::NonClientAreaMouseMove) { + // if we're ready to start a drag, check how far we've moved and start the drag if past a + // certain pixel threshold. + if (m_dragReady) { + if ((QCursor::pos() - m_dragStartCursor).manhattanLength() > 10) { + m_dragActive = true; + m_dragReady = false; + QList toolWindows; + foreach(ToolWindowManagerArea* tabWidget, findChildren()) { + if (ToolWindowManager::managerOf(tabWidget) == m_manager) { + toolWindows << tabWidget->toolWindows(); + } + } + m_manager->startDrag(toolWindows, this); + } + } + // if the drag is active, update it in the manager. + if (m_dragActive) { + m_manager->updateDragPosition(); + + // on non-windows we have no native title bar, so we need to move the window ourselves +#if !defined(Q_OS_WIN32) + move(QCursor::pos() - (m_dragStartCursor - m_dragStartGeometry.topLeft())); +#endif + } + if (titleRect().contains(mapFromGlobal(QCursor::pos()))) { + // if we're in the title bar, repaint to pick up motion over the close button + update(); + } + + ResizeDirection dir = checkResize(); + + if (m_dragDirection != ResizeDirection::Count) { + dir = m_dragDirection; + + QRect g = geometry(); + + switch (dir) { + case ResizeDirection::NW: + g.setTopLeft(QCursor::pos()); + break; + case ResizeDirection::NE: + g.setTopRight(QCursor::pos()); + break; + case ResizeDirection::SW: + g.setBottomLeft(QCursor::pos()); + break; + case ResizeDirection::SE: + g.setBottomRight(QCursor::pos()); + break; + case ResizeDirection::N: + g.setTop(QCursor::pos().y()); + break; + case ResizeDirection::E: + g.setRight(QCursor::pos().x()); + break; + case ResizeDirection::S: + g.setBottom(QCursor::pos().y()); + break; + case ResizeDirection::W: + g.setLeft(QCursor::pos().x()); + break; + case ResizeDirection::Count: + break; + } + + setGeometry(g); + } + + if (dir != ResizeDirection::Count) { + setCursor(shapes[(int)dir]); + + QObjectList children = this->children(); + for (int i = 0; i < children.size(); ++i) { + if (QWidget *w = qobject_cast(children.at(i))) { + if (!w->testAttribute(Qt::WA_SetCursor)) { + w->setCursor(Qt::ArrowCursor); + } + } + } + } else { + unsetCursor(); + } + + } else if (event->type() == QEvent::MouseButtonPress) { + ResizeDirection dir = checkResize(); + m_dragStartCursor = QCursor::pos(); + m_dragStartGeometry = geometry(); + if (dir == ResizeDirection::Count) + m_dragReady = true; + else + m_dragDirection = dir; + } else if (event->type() == QEvent::NonClientAreaMouseButtonPress) { + m_dragActive = true; + m_dragReady = false; + m_dragStartCursor = QCursor::pos(); + m_dragStartGeometry = geometry(); + QList toolWindows; + foreach(ToolWindowManagerArea* tabWidget, findChildren()) { + if (ToolWindowManager::managerOf(tabWidget) == m_manager) { + toolWindows << tabWidget->toolWindows(); + } + } + m_manager->startDrag(toolWindows, this); + } else if (event->type() == QEvent::Move && m_dragActive) { + m_manager->updateDragPosition(); + m_moveTimeout->start(); + } else if (event->type() == QEvent::Leave) { + unsetCursor(); + } else if (event->type() == QEvent::MouseButtonDblClick && + titleRect().contains(mapFromGlobal(QCursor::pos()))) { + if (isMaximized()) { + showNormal(); + } else { + showMaximized(); + } + } + } + return QWidget::eventFilter(object, event); +} + +void ToolWindowManagerWrapper::paintEvent(QPaintEvent *) { + if (!m_floating || m_titleHeight == 0) + return; + + { + QStylePainter p(this); + + QStyleOptionFrame frameOptions; + frameOptions.init(this); + p.drawPrimitive(QStyle::PE_FrameDockWidget, frameOptions); + + // Title must be painted after the frame, since the areas overlap, and + // the title may wish to extend out to all sides (eg. XP style) + QStyleOptionDockWidget titlebarOptions; + + titlebarOptions.initFrom(this); + titlebarOptions.rect = titleRect(); + titlebarOptions.title = windowTitle(); + titlebarOptions.closable = true; + titlebarOptions.movable = true; + titlebarOptions.floatable = false; + titlebarOptions.verticalTitleBar = false; + + p.drawControl(QStyle::CE_DockWidgetTitle, titlebarOptions); + + QStyleOptionToolButton buttonOpt; + + buttonOpt.initFrom(this); + buttonOpt.iconSize = QSize(m_closeButtonSize, m_closeButtonSize); + buttonOpt.subControls = 0; + buttonOpt.activeSubControls = 0; + buttonOpt.features = QStyleOptionToolButton::None; + buttonOpt.arrowType = Qt::NoArrow; + buttonOpt.state = QStyle::State_Active|QStyle::State_Enabled|QStyle::State_AutoRaise; + + if (m_closeRect.contains(mapFromGlobal(QCursor::pos()))) { + buttonOpt.state |= QStyle::State_MouseOver|QStyle::State_Raised; + } + + buttonOpt.rect = m_closeRect; + buttonOpt.icon = m_closeIcon; + + if (style()->styleHint(QStyle::SH_DockWidget_ButtonsHaveFrame, 0, this)) { + style()->drawPrimitive(QStyle::PE_PanelButtonTool, &buttonOpt, &p, this); + } + + style()->drawComplexControl(QStyle::CC_ToolButton, &buttonOpt, &p, this); + } +} + +void ToolWindowManagerWrapper::resizeEvent(QResizeEvent *) +{ + QStyleOptionDockWidget option; + + option.initFrom(this); + option.rect = titleRect(); + option.closable = true; + option.movable = true; + option.floatable = true; + + m_closeRect = style()->subElementRect(QStyle::SE_DockWidgetCloseButton, &option, this); + m_closeIcon = style()->standardIcon(QStyle::SP_TitleBarCloseButton, &option, this); +} + +QRect ToolWindowManagerWrapper::titleRect() +{ + QRect ret; + + ret.setTopLeft(QPoint(m_frameWidth, m_frameWidth)); + ret.setSize(QSize(geometry().width() - (m_frameWidth * 2), m_titleHeight)); + + return ret; +} + +QVariantMap ToolWindowManagerWrapper::saveState() { + if (layout()->count() > 2) { + qWarning("too many children for wrapper"); + return QVariantMap(); + } + if (isWindow() && layout()->count() == 0) { + qWarning("empty top level wrapper"); + return QVariantMap(); + } + QVariantMap result; + result[QStringLiteral("geometry")] = saveGeometry().toBase64(); + QSplitter* splitter = findChild(QString(), Qt::FindDirectChildrenOnly); + if (splitter) { + result[QStringLiteral("splitter")] = m_manager->saveSplitterState(splitter); + } else { + ToolWindowManagerArea* area = findChild(); + if (area) { + result[QStringLiteral("area")] = area->saveState(); + } else if (layout()->count() > 0) { + qWarning("unknown child"); + return QVariantMap(); + } + } + return result; +} + +void ToolWindowManagerWrapper::restoreState(const QVariantMap &savedData) { + restoreGeometry(QByteArray::fromBase64(savedData[QStringLiteral("geometry")].toByteArray())); + if (layout()->count() > 1) { + qWarning("wrapper is not empty"); + return; + } + if (savedData.contains(QStringLiteral("splitter"))) { + layout()->addWidget(m_manager->restoreSplitterState(savedData[QStringLiteral("splitter")].toMap())); + } else if (savedData.contains(QStringLiteral("area"))) { + ToolWindowManagerArea* area = m_manager->createArea(); + area->restoreState(savedData[QStringLiteral("area")].toMap()); + layout()->addWidget(area); + } +} + +void ToolWindowManagerWrapper::moveTimeout() { + m_manager->updateDragPosition(); + + if (!m_manager->dragInProgress()) { + m_moveTimeout->stop(); + } +} + +ToolWindowManagerWrapper::ResizeDirection ToolWindowManagerWrapper::checkResize() { + if (m_titleHeight == 0) + return ResizeDirection::Count; + + // check if we should offer to resize + QRect rect = this->rect(); + QPoint testPos = mapFromGlobal(QCursor::pos()); + + if (m_closeRect.contains(testPos)) + return ResizeDirection::Count; + + const int resizeMargin = 4; + + if (rect.contains(testPos)) { + // check corners first, then horizontal/vertical + if (testPos.x() < rect.x() + resizeMargin*4 && testPos.y() < rect.y() + resizeMargin*4) { + return ResizeDirection::NW; + } else if (testPos.x() > rect.width() - resizeMargin*4 && testPos.y() < rect.y() + resizeMargin*4) { + return ResizeDirection::NE; + } else if (testPos.x() < rect.x() + resizeMargin*4 && testPos.y() > rect.height() - resizeMargin*4) { + return ResizeDirection::SW; + } else if (testPos.x() > rect.width() - resizeMargin*4 && testPos.y() > rect.height() - resizeMargin*4) { + return ResizeDirection::SE; + } else if (testPos.x() < rect.x() + resizeMargin) { + return ResizeDirection::W; + } else if (testPos.x() > rect.width() - resizeMargin) { + return ResizeDirection::E; + } else if (testPos.y() < rect.y() + resizeMargin) { + return ResizeDirection::N; + } else if (testPos.y() > rect.height() - resizeMargin) { + return ResizeDirection::S; + } + } + + return ResizeDirection::Count; +} diff --git a/tools/leveleditor/CMakeLists.txt b/tools/leveleditor/CMakeLists.txt index 77fb5cc..40adaab 100644 --- a/tools/leveleditor/CMakeLists.txt +++ b/tools/leveleditor/CMakeLists.txt @@ -14,4 +14,4 @@ add_executable(LevelEditor src/editorstyle.cpp ${EDITOR_SRC}) target_include_directories(LevelEditor PRIVATE include) -target_link_libraries(LevelEditor Qt5::Widgets Engine) +target_link_libraries(LevelEditor Qt5::Widgets Engine ToolWindowManager) diff --git a/tools/leveleditor/src/editorstyle.cpp b/tools/leveleditor/src/editorstyle.cpp index 2b0e94f..f452833 100644 --- a/tools/leveleditor/src/editorstyle.cpp +++ b/tools/leveleditor/src/editorstyle.cpp @@ -10,19 +10,13 @@ void EditorStyle::drawPrimitive(PrimitiveElement pe, const QStyleOption *opt, QP switch(pe) { case PE_FrameMenu: - { p->fillRect(x, y, width, height, QColor(75, 75, 80)); - } break; case PE_PanelMenu: - { p->fillRect(x, y, width, height, QColor(50, 50, 55)); - } break; case PE_PanelMenuBar: - { p->fillRect(x, y, width, height, QColor(75, 75, 80)); - } break; default: QProxyStyle::drawPrimitive(pe, opt, p, widget); @@ -48,6 +42,8 @@ void EditorStyle::drawControl(ControlElement element, const QStyleOption* option if(enabled && (selected || hovered)) painter->fillRect(option->rect, option->palette.brush(QPalette::Highlight)); + else + painter->fillRect(option->rect, QColor(50, 50, 55)); drawItemText(painter, option->rect, Qt::AlignCenter | Qt::TextShowMnemonic | Qt::TextDontClip | Qt::TextSingleLine, menuopt->palette, true, menuopt->text, QPalette::ButtonText); } @@ -58,7 +54,7 @@ void EditorStyle::drawControl(ControlElement element, const QStyleOption* option if(menuopt->menuItemType == QStyleOptionMenuItem::Separator) { painter->setPen(QColor(75, 75, 80)); - painter->drawLine(x + 5, y + 1, x + width - 5, y + 1); + painter->drawLine(x + 5, y + 2, x + width - 5, y + 2); } else { const bool selected = menuopt->state & State_Selected; const bool hovered = menuopt->state & State_MouseOver; @@ -67,11 +63,12 @@ void EditorStyle::drawControl(ControlElement element, const QStyleOption* option QPixmap pix = menuopt->icon.pixmap(16, 16, (menuopt->state & State_Enabled) ? QIcon::Normal : QIcon::Disabled); QRect rect = option->rect; - rect.adjust(0, 4, 0, 0); if(enabled && (selected || hovered)) painter->fillRect(rect, option->palette.brush(QPalette::Highlight)); + rect.adjust(0, 4, 0, 0); + if(!pix.isNull()) { const QRect iconRect = option->rect.adjusted(5, 5, 0, 0); @@ -89,12 +86,41 @@ void EditorStyle::drawControl(ControlElement element, const QStyleOption* option drawItemText(painter, rect, Qt::AlignLeft | Qt::TextShowMnemonic | Qt::TextDontClip | Qt::TextSingleLine, menuopt->palette, true, label, QPalette::ButtonText); if(index != -1) { - painter->setPen(QColor(150, 150, 150)); + if(enabled && (selected || hovered)) + painter->setPen(Qt::white); + else + painter->setPen(QColor(150, 150, 150)); + drawItemText(painter, rect.adjusted(0, 0, -10, 0), Qt::AlignRight | Qt::TextShowMnemonic | Qt::TextDontClip | Qt::TextSingleLine, menuopt->palette, true, shortcut); } } } break; + case CE_TabBarTab: + { + const QStyleOptionTab* menuopt = qstyleoption_cast(option); + + painter->setPen(QColor(75, 75, 80)); + painter->setBrush(QColor(50, 50, 55)); + + painter->drawRect(option->rect); + + QRect rect = option->rect; + rect.adjust(10, 5, 0, 0); + + QPixmap pix = menuopt->icon.pixmap(16, 16, (menuopt->state & State_Enabled) ? QIcon::Normal : QIcon::Disabled); + + if(!pix.isNull()) { + const QRect iconRect = option->rect.adjusted(5, 6, 0, 0); + + drawItemPixmap(painter, iconRect, 0, pix); + + rect.adjust(16, 0, 0, 0); + } + + drawItemText(painter, rect, Qt::AlignLeft | Qt::TextShowMnemonic | Qt::TextDontClip | Qt::TextSingleLine, menuopt->palette, true, menuopt->text, QPalette::ButtonText); + } + break; default: QProxyStyle::drawControl(element, option, painter, widget); break; @@ -134,6 +160,7 @@ int EditorStyle::pixelMetric(PixelMetric metric, const QStyleOption* option, con } void EditorStyle::polish(QPalette& palette) { - palette.setBrush(QPalette::Button, QColor(50, 50, 50)); + palette.setBrush(QPalette::Window, QColor(45, 45, 45)); + palette.setBrush(QPalette::Button, QColor(50, 50, 55)); } diff --git a/tools/leveleditor/src/mainwindow.cpp b/tools/leveleditor/src/mainwindow.cpp index f3f9271..36116e1 100644 --- a/tools/leveleditor/src/mainwindow.cpp +++ b/tools/leveleditor/src/mainwindow.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "renderwindow.h" #include "renderer.h" @@ -87,5 +88,11 @@ MainWindow::MainWindow(Context& context) { window->setVulkanInstance(instance); QWidget* wrapper = QWidget::createWindowContainer(window); - setCentralWidget(wrapper); + wrapper->setWindowIcon(QIcon::fromTheme("view-fullscreen")); + wrapper->setWindowTitle("Scene View"); + + ToolWindowManager* manager = new ToolWindowManager(); + setCentralWidget(manager); + + manager->addToolWindow(wrapper, ToolWindowManager::AreaReference(ToolWindowManager::EmptySpace)); }