├── .clang-format ├── .gitignore ├── .travis.yml ├── CMakeLists.txt ├── LICENSE ├── README.md ├── docs ├── osx.png ├── plasma.png ├── unity.png └── windows.png ├── example ├── CMakeLists.txt ├── MainWindow.cpp ├── MainWindow.h ├── MainWindow.ui ├── ToolWindowManager.pro └── main.cpp └── src ├── CMakeLists.txt ├── ToolWindowManager.cpp ├── ToolWindowManager.h ├── ToolWindowManagerArea.cpp ├── ToolWindowManagerArea.h ├── ToolWindowManagerSplitter.cpp ├── ToolWindowManagerSplitter.h ├── ToolWindowManagerTabBar.cpp ├── ToolWindowManagerTabBar.h ├── ToolWindowManagerWrapper.cpp └── ToolWindowManagerWrapper.h /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: Chromium 4 | AccessModifierOffset: -2 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveAssignments: false 7 | AlignConsecutiveDeclarations: false 8 | AlignEscapedNewlinesLeft: true 9 | AlignOperands: true 10 | AlignTrailingComments: true 11 | AllowAllParametersOfDeclarationOnNextLine: false 12 | AllowShortBlocksOnASingleLine: false 13 | AllowShortCaseLabelsOnASingleLine: true 14 | AllowShortFunctionsOnASingleLine: Inline 15 | AllowShortIfStatementsOnASingleLine: false 16 | AllowShortLoopsOnASingleLine: false 17 | AlwaysBreakAfterDefinitionReturnType: None 18 | AlwaysBreakAfterReturnType: None 19 | AlwaysBreakBeforeMultilineStrings: true 20 | AlwaysBreakTemplateDeclarations: true 21 | BinPackArguments: true 22 | BinPackParameters: true 23 | BraceWrapping: 24 | AfterClass: true 25 | AfterControlStatement: true 26 | AfterEnum: true 27 | AfterFunction: true 28 | AfterNamespace: true 29 | AfterObjCDeclaration: true 30 | AfterStruct: true 31 | AfterUnion: true 32 | BeforeCatch: true 33 | BeforeElse: true 34 | IndentBraces: false 35 | BreakBeforeBinaryOperators: None 36 | BreakBeforeBraces: Custom 37 | BreakBeforeTernaryOperators: true 38 | BreakConstructorInitializersBeforeComma: false 39 | ColumnLimit: 100 40 | CommentPragmas: '^ IWYU pragma:' 41 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 42 | ConstructorInitializerIndentWidth: 4 43 | ContinuationIndentWidth: 4 44 | Cpp11BracedListStyle: true 45 | DerivePointerAlignment: false 46 | DisableFormat: false 47 | ExperimentalAutoDetectBinPacking: false 48 | ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] 49 | IncludeCategories: 50 | - Regex: '<.*\.h>' 51 | Priority: 1 52 | - Regex: '^<[^.]*>' 53 | Priority: 2 54 | - Regex: '.*/.*' 55 | Priority: 3 56 | - Regex: '.*' 57 | Priority: 4 58 | IndentCaseLabels: true 59 | IndentWidth: 2 60 | IndentWrappedFunctionNames: false 61 | KeepEmptyLinesAtTheStartOfBlocks: false 62 | MacroBlockBegin: '' 63 | MacroBlockEnd: '' 64 | MaxEmptyLinesToKeep: 1 65 | NamespaceIndentation: None 66 | ObjCBlockIndentWidth: 2 67 | ObjCSpaceAfterProperty: false 68 | ObjCSpaceBeforeProtocolList: false 69 | PenaltyBreakBeforeFirstCallParameter: 1 70 | PenaltyBreakComment: 300 71 | PenaltyBreakFirstLessLess: 120 72 | PenaltyBreakString: 1000 73 | PenaltyExcessCharacter: 10 74 | PenaltyReturnTypeOnItsOwnLine: 2000 75 | PointerAlignment: Right 76 | ReflowComments: true 77 | SortIncludes: true 78 | SpaceAfterCStyleCast: false 79 | SpaceBeforeAssignmentOperators: true 80 | SpaceBeforeParens: Never 81 | SpaceInEmptyParentheses: false 82 | SpacesBeforeTrailingComments: 4 83 | SpacesInAngles: false 84 | SpacesInContainerLiterals: true 85 | SpacesInCStyleCastParentheses: false 86 | SpacesInParentheses: false 87 | SpacesInSquareBrackets: false 88 | Standard: Auto 89 | TabWidth: 2 90 | UseTab: Never 91 | ... 92 | 93 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.user 2 | build*/ 3 | example/debug/ 4 | example/release/ 5 | example/.vs/ 6 | example/*.sln 7 | example/*.vcxproj* 8 | example/ui_*.h 9 | *~ 10 | 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | sudo: required 3 | dist: trusty 4 | 5 | branches: 6 | only: 7 | - renderdoc 8 | 9 | os: linux 10 | compiler: gcc 11 | 12 | # install dependencies and check clang-format 13 | install: 14 | - sudo add-apt-repository -y 'ppa:beineri/opt-qt562-trusty' 15 | - sudo add-apt-repository -y 'ppa:ubuntu-toolchain-r/test' 16 | - sudo add-apt-repository -y 'deb http://apt.llvm.org/precise/ llvm-toolchain-precise-3.8 main' 17 | - wget -O - http://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - 18 | - sudo apt-get update -qq 19 | - sudo apt-get install --allow-unauthenticated -y -qq qt56base clang-format-3.8 g++-6 20 | - clang-format-3.8 -i -style=file $(find src/ example/ -type f -regex '.*\.\(cpp\|h\)$' -print) 21 | - git clean -f 22 | - git diff --quiet || echo "Please ensure code is formatted with clang-format-3.8 specifically" 23 | - git diff --exit-code 24 | 25 | script: 26 | - . /opt/qt56/bin/qt56-env.sh 27 | - cd example 28 | - qmake QMAKE_CXX=g++-6 QMAKE_CXXFLAGS=-std=c++11 . 29 | - make -j2 30 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8) 2 | 3 | add_subdirectory(src) 4 | 5 | option (TWM_BUILD_EXAMPLE "Build example" ON) 6 | 7 | if (TWM_BUILD_EXAMPLE) 8 | add_subdirectory(example) 9 | endif() -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Pavel Strakhov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ToolWindowManager 2 | ================= 3 | 4 | ToolWindowManager is a Qt based tool window manager. This allows Qt projects to use docking functionality similar to QDockWidget, but since it's a separate component it's easier to customise and extend and so has a number of improvements over the built-in docking system. 5 | 6 | This is a fork from https://github.com/riateche/toolwindowmanager, specifically from [5244be3f9ac680ac568a6eff8156a520ce08ecf1](https://github.com/baldurk/toolwindowmanager/tree/original_impl) where the license is clearly MIT. After that point there was a re-implementation that may have relicensed under LGPL and the author has not clarified. 7 | 8 | Also this fork contains a number of changes and improvements to make it useful for RenderDoc and perhaps other projects. Notable highlights: 9 | 10 | * Additional customisability like arbitrary data tagged with saved states, callbacks to check before closing, and allowing/disallowing tab reorder or float windows 11 | * Fixes for having multiple nested TWMs 12 | * Render a preview overlay for drop locations 13 | * Use hotspots icons and specific locations to determine drop sites, not the old 'cycle through suggestions' method 14 | * Allow dragging/dropping whole floating windows together 15 | 16 | The original README.md [can be found here](https://github.com/baldurk/toolwindowmanager/blob/original_impl/README.md) 17 | 18 | Screenshots 19 | =========== 20 | 21 | Windows: 22 | 23 | ![Windows](docs/windows.png) 24 | 25 | 26 | Linux (Ubuntu 17.04 Unity); 27 | 28 | ![Ubuntu 17.04 Unity](docs/unity.png) 29 | 30 | Linux (Ubuntu 17.04 KDE Plasma): 31 | 32 | ![Ubuntu 17.04 KDE Plasma](docs/plasma.png) 33 | 34 | OS X: 35 | 36 | ![OS X](docs/osx.png) 37 | -------------------------------------------------------------------------------- /docs/osx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baldurk/toolwindowmanager/106405739ef8b57d1f6520af0d02023042d20a90/docs/osx.png -------------------------------------------------------------------------------- /docs/plasma.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baldurk/toolwindowmanager/106405739ef8b57d1f6520af0d02023042d20a90/docs/plasma.png -------------------------------------------------------------------------------- /docs/unity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baldurk/toolwindowmanager/106405739ef8b57d1f6520af0d02023042d20a90/docs/unity.png -------------------------------------------------------------------------------- /docs/windows.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baldurk/toolwindowmanager/106405739ef8b57d1f6520af0d02023042d20a90/docs/windows.png -------------------------------------------------------------------------------- /example/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(twm-example) 2 | 3 | set(SOURCES 4 | main.cpp 5 | MainWindow.cpp) 6 | 7 | set(HEADERS 8 | MainWindow.h) 9 | 10 | set(MOC_SOURCES 11 | MainWindow.h) 12 | 13 | set(UI_FILES 14 | MainWindow.ui) 15 | 16 | find_package(Qt5Core) 17 | find_package(Qt5Widgets) 18 | 19 | qt5_wrap_ui(OUT_UI_FILES ${UI_FILES}) 20 | qt5_wrap_cpp(OUT_MOC_FILES ${MOC_SOURCES}) 21 | 22 | add_executable(twm_example WIN32 ${SOURCES} ${HEADERS} 23 | ${OUT_UI_FILES} ${OUT_MOC_FILES}) 24 | target_link_libraries(twm_example toolwindowmanager) 25 | target_include_directories(twm_example 26 | PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) 27 | 28 | if(WIN32) 29 | find_package(Qt5WinExtras) 30 | target_link_libraries(twm_example Qt5::WinExtras) 31 | endif() -------------------------------------------------------------------------------- /example/MainWindow.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014 Pavel Strakhov 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | * 24 | */ 25 | #include "MainWindow.h" 26 | #include 27 | #include 28 | #include 29 | #include "ui_MainWindow.h" 30 | 31 | MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) 32 | { 33 | ui->setupUi(this); 34 | setAttribute(Qt::WA_DeleteOnClose); 35 | connect(ui->toolWindowManager, SIGNAL(toolWindowVisibilityChanged(QWidget *, bool)), this, 36 | SLOT(toolWindowVisibilityChanged(QWidget *, bool))); 37 | 38 | QList toolWindows; 39 | for(int i = 0; i < 6; i++) 40 | { 41 | QPushButton *b1 = new QPushButton(QStringLiteral("tool%1").arg(i + 1)); 42 | b1->setWindowTitle(b1->text()); 43 | b1->setObjectName(b1->text()); 44 | QAction *action = ui->menuToolWindows->addAction(b1->text()); 45 | action->setData(i); 46 | action->setCheckable(true); 47 | action->setChecked(true); 48 | connect(action, SIGNAL(triggered(bool)), this, SLOT(toolWindowActionToggled(bool))); 49 | actions << action; 50 | toolWindows << b1; 51 | } 52 | ui->toolWindowManager->addToolWindow(toolWindows[0], ToolWindowManager::EmptySpace); 53 | ui->toolWindowManager->addToolWindow(toolWindows[1], ToolWindowManager::LastUsedArea); 54 | ui->toolWindowManager->addToolWindow(toolWindows[2], ToolWindowManager::LastUsedArea); 55 | ui->toolWindowManager->addToolWindow( 56 | toolWindows[3], ToolWindowManager::AreaReference( 57 | ToolWindowManager::LeftOf, ui->toolWindowManager->areaOf(toolWindows[2]))); 58 | ui->toolWindowManager->addToolWindow(toolWindows[4], ToolWindowManager::LastUsedArea); 59 | ui->toolWindowManager->addToolWindow( 60 | toolWindows[5], ToolWindowManager::AreaReference( 61 | ToolWindowManager::TopOf, ui->toolWindowManager->areaOf(toolWindows[4]))); 62 | 63 | resize(600, 400); 64 | on_actionRestoreState_triggered(); 65 | } 66 | 67 | MainWindow::~MainWindow() 68 | { 69 | delete ui; 70 | } 71 | 72 | void MainWindow::toolWindowActionToggled(bool state) 73 | { 74 | int index = static_cast(sender())->data().toInt(); 75 | QWidget *toolWindow = ui->toolWindowManager->toolWindows()[index]; 76 | ui->toolWindowManager->moveToolWindow( 77 | toolWindow, state ? ToolWindowManager::LastUsedArea : ToolWindowManager::NoArea); 78 | } 79 | 80 | void MainWindow::toolWindowVisibilityChanged(QWidget *toolWindow, bool visible) 81 | { 82 | int index = ui->toolWindowManager->toolWindows().indexOf(toolWindow); 83 | actions[index]->blockSignals(true); 84 | actions[index]->setChecked(visible); 85 | actions[index]->blockSignals(false); 86 | } 87 | 88 | void MainWindow::on_actionSaveState_triggered() 89 | { 90 | QSettings settings; 91 | settings.setValue(QStringLiteral("toolWindowManagerState"), ui->toolWindowManager->saveState()); 92 | settings.setValue(QStringLiteral("geometry"), saveGeometry()); 93 | } 94 | 95 | void MainWindow::on_actionRestoreState_triggered() 96 | { 97 | QSettings settings; 98 | restoreGeometry(settings.value(QStringLiteral("geometry")).toByteArray()); 99 | ui->toolWindowManager->restoreState( 100 | settings.value(QStringLiteral("toolWindowManagerState")).toMap()); 101 | } 102 | 103 | void MainWindow::on_actionClearState_triggered() 104 | { 105 | QSettings settings; 106 | settings.remove(QStringLiteral("geometry")); 107 | settings.remove(QStringLiteral("toolWindowManagerState")); 108 | QApplication::quit(); 109 | } 110 | -------------------------------------------------------------------------------- /example/MainWindow.h: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014 Pavel Strakhov 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | * 24 | */ 25 | #ifndef MAINWINDOW_H 26 | #define MAINWINDOW_H 27 | 28 | #include 29 | 30 | namespace Ui 31 | { 32 | class MainWindow; 33 | } 34 | 35 | class MainWindow : public QMainWindow 36 | { 37 | Q_OBJECT 38 | 39 | public: 40 | explicit MainWindow(QWidget *parent = 0); 41 | ~MainWindow(); 42 | 43 | private: 44 | Ui::MainWindow *ui; 45 | QList actions; 46 | 47 | private slots: 48 | void toolWindowActionToggled(bool state); 49 | void toolWindowVisibilityChanged(QWidget *toolWindow, bool visible); 50 | void on_actionSaveState_triggered(); 51 | void on_actionRestoreState_triggered(); 52 | void on_actionClearState_triggered(); 53 | }; 54 | 55 | #endif // MAINWINDOW_H 56 | -------------------------------------------------------------------------------- /example/MainWindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 400 10 | 300 11 | 12 | 13 | 14 | MainWindow 15 | 16 | 17 | 18 | 19 | 0 20 | 21 | 22 | 0 23 | 24 | 25 | 0 26 | 27 | 28 | 0 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 0 39 | 0 40 | 400 41 | 22 42 | 43 | 44 | 45 | 46 | Tool windows 47 | 48 | 49 | 50 | 51 | Storage 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | Save state 64 | 65 | 66 | 67 | 68 | Restore state 69 | 70 | 71 | 72 | 73 | Clear state and exit 74 | 75 | 76 | 77 | 78 | 79 | 80 | ToolWindowManager 81 | QWidget 82 |
ToolWindowManager.h
83 | 1 84 |
85 |
86 | 87 | 88 |
89 | -------------------------------------------------------------------------------- /example/ToolWindowManager.pro: -------------------------------------------------------------------------------- 1 | #------------------------------------------------- 2 | # 3 | # Project created by QtCreator 2014-03-07T22:02:13 4 | # 5 | #------------------------------------------------- 6 | 7 | QT += core gui widgets 8 | 9 | lessThan(QT_MAJOR_VERSION, 5): error("requires Qt 5; found $$[QT_VERSION]") 10 | 11 | TARGET = ToolWindowManager 12 | TEMPLATE = app 13 | 14 | 15 | # Disable conversions to/from const char * in QString 16 | DEFINES += QT_NO_CAST_FROM_ASCII QT_NO_CAST_TO_ASCII 17 | 18 | INCLUDEPATH += ../src 19 | 20 | SOURCES += main.cpp\ 21 | MainWindow.cpp \ 22 | ../src/ToolWindowManager.cpp \ 23 | ../src/ToolWindowManagerArea.cpp \ 24 | ../src/ToolWindowManagerSplitter.cpp \ 25 | ../src/ToolWindowManagerTabBar.cpp \ 26 | ../src/ToolWindowManagerWrapper.cpp 27 | 28 | HEADERS += MainWindow.h \ 29 | ../src/ToolWindowManager.h \ 30 | ../src/ToolWindowManagerArea.h \ 31 | ../src/ToolWindowManagerSplitter.h \ 32 | ../src/ToolWindowManagerTabBar.h \ 33 | ../src/ToolWindowManagerWrapper.h 34 | 35 | FORMS += MainWindow.ui 36 | -------------------------------------------------------------------------------- /example/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014 Pavel Strakhov 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | * 24 | */ 25 | #include 26 | #include "MainWindow.h" 27 | 28 | int main(int argc, char *argv[]) 29 | { 30 | QApplication a(argc, argv); 31 | a.setOrganizationName(QStringLiteral("ToolWindowManagerTest")); 32 | a.setApplicationName(QStringLiteral("ToolWindowManagerTest")); 33 | MainWindow *w = new MainWindow(); 34 | w->show(); 35 | 36 | return a.exec(); 37 | } 38 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(toolwindowmanager) 2 | set(SOURCES 3 | ToolWindowManager.cpp 4 | ToolWindowManagerArea.cpp 5 | ToolWindowManagerSplitter.cpp 6 | ToolWindowManagerTabBar.cpp 7 | ToolWindowManagerWrapper.cpp 8 | ) 9 | 10 | set(HEADERS 11 | ToolWindowManager.h 12 | ToolWindowManagerArea.h 13 | ToolWindowManagerSplitter.h 14 | ToolWindowManagerTabBar.h 15 | ToolWindowManagerWrapper.h 16 | ) 17 | 18 | set(MOC_SOURCES 19 | ${HEADERS}) 20 | 21 | find_package(Qt5Core) 22 | find_package(Qt5Widgets) 23 | 24 | qt5_wrap_cpp(OUT_MOC_FILES ${MOC_SOURCES}) 25 | 26 | add_library(toolwindowmanager ${SOURCES} ${HEADERS} ${OUT_MOC_FILES}) 27 | target_include_directories(toolwindowmanager 28 | PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) 29 | target_link_libraries(toolwindowmanager 30 | Qt5::Core Qt5::Gui Qt5::Widgets) -------------------------------------------------------------------------------- /src/ToolWindowManager.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014 Pavel Strakhov 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | * 24 | */ 25 | #include "ToolWindowManager.h" 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include "ToolWindowManagerArea.h" 41 | #include "ToolWindowManagerSplitter.h" 42 | #include "ToolWindowManagerWrapper.h" 43 | 44 | template 45 | T findClosestParent(QWidget *widget) 46 | { 47 | while(widget) 48 | { 49 | if(qobject_cast(widget)) 50 | { 51 | return static_cast(widget); 52 | } 53 | widget = widget->parentWidget(); 54 | } 55 | return 0; 56 | } 57 | 58 | ToolWindowManager::ToolWindowManager(QWidget *parent) : QWidget(parent) 59 | { 60 | QVBoxLayout *mainLayout = new QVBoxLayout(this); 61 | mainLayout->setContentsMargins(0, 0, 0, 0); 62 | ToolWindowManagerWrapper *wrapper = new ToolWindowManagerWrapper(this, false); 63 | wrapper->setWindowFlags(wrapper->windowFlags() & ~Qt::Tool); 64 | mainLayout->addWidget(wrapper); 65 | m_allowFloatingWindow = true; 66 | m_createCallback = NULL; 67 | m_lastUsedArea = NULL; 68 | 69 | m_draggedWrapper = NULL; 70 | m_hoverArea = NULL; 71 | 72 | QPalette pal = palette(); 73 | pal.setColor(QPalette::Background, pal.color(QPalette::Highlight)); 74 | 75 | m_previewOverlay = new QWidget(NULL); 76 | m_previewOverlay->setAutoFillBackground(true); 77 | m_previewOverlay->setPalette(pal); 78 | m_previewOverlay->setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | 79 | Qt::X11BypassWindowManagerHint); 80 | m_previewOverlay->setWindowOpacity(0.3); 81 | m_previewOverlay->setAttribute(Qt::WA_ShowWithoutActivating); 82 | m_previewOverlay->setAttribute(Qt::WA_AlwaysStackOnTop); 83 | m_previewOverlay->hide(); 84 | 85 | m_previewTabOverlay = new QWidget(NULL); 86 | m_previewTabOverlay->setAutoFillBackground(true); 87 | m_previewTabOverlay->setPalette(pal); 88 | m_previewTabOverlay->setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | 89 | Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint); 90 | m_previewTabOverlay->setWindowOpacity(0.3); 91 | m_previewTabOverlay->setAttribute(Qt::WA_ShowWithoutActivating); 92 | m_previewTabOverlay->setAttribute(Qt::WA_AlwaysStackOnTop); 93 | m_previewTabOverlay->hide(); 94 | 95 | for(int i = 0; i < NumReferenceTypes; i++) 96 | m_dropHotspots[i] = NULL; 97 | 98 | m_dropHotspotDimension = 32; 99 | m_dropHotspotMargin = 4; 100 | 101 | drawHotspotPixmaps(); 102 | 103 | for(AreaReferenceType type : {AddTo, TopOf, LeftOf, RightOf, BottomOf, TopWindowSide, 104 | LeftWindowSide, RightWindowSide, BottomWindowSide}) 105 | { 106 | m_dropHotspots[type] = new QLabel(NULL); 107 | m_dropHotspots[type]->setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | 108 | Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint); 109 | m_dropHotspots[type]->setAttribute(Qt::WA_ShowWithoutActivating); 110 | m_dropHotspots[type]->setAttribute(Qt::WA_AlwaysStackOnTop); 111 | m_dropHotspots[type]->setPixmap(m_pixmaps[type]); 112 | m_dropHotspots[type]->setFixedSize(m_dropHotspotDimension, m_dropHotspotDimension); 113 | } 114 | } 115 | 116 | ToolWindowManager::~ToolWindowManager() 117 | { 118 | delete m_previewOverlay; 119 | delete m_previewTabOverlay; 120 | for(QWidget *hotspot : m_dropHotspots) 121 | delete hotspot; 122 | while(!m_areas.isEmpty()) 123 | { 124 | delete m_areas.first(); 125 | } 126 | while(!m_wrappers.isEmpty()) 127 | { 128 | delete m_wrappers.first(); 129 | } 130 | } 131 | 132 | void ToolWindowManager::setToolWindowProperties(QWidget *toolWindow, 133 | ToolWindowManager::ToolWindowProperty properties) 134 | { 135 | m_toolWindowProperties[toolWindow] = properties; 136 | ToolWindowManagerArea *area = areaOf(toolWindow); 137 | if(area) 138 | area->updateToolWindow(toolWindow); 139 | } 140 | 141 | ToolWindowManager::ToolWindowProperty ToolWindowManager::toolWindowProperties(QWidget *toolWindow) 142 | { 143 | return m_toolWindowProperties[toolWindow]; 144 | } 145 | 146 | void ToolWindowManager::addToolWindow(QWidget *toolWindow, const AreaReference &area, 147 | ToolWindowManager::ToolWindowProperty properties) 148 | { 149 | addToolWindows(QList() << toolWindow, area, properties); 150 | } 151 | 152 | void ToolWindowManager::addToolWindows(QList toolWindows, 153 | const ToolWindowManager::AreaReference &area, 154 | ToolWindowManager::ToolWindowProperty properties) 155 | { 156 | foreach(QWidget *toolWindow, toolWindows) 157 | { 158 | if(!toolWindow) 159 | { 160 | qWarning("cannot add null widget"); 161 | continue; 162 | } 163 | if(m_toolWindows.contains(toolWindow)) 164 | { 165 | qWarning() << "this tool window has already been added" << toolWindow->objectName(); 166 | continue; 167 | } 168 | toolWindow->hide(); 169 | toolWindow->setParent(0); 170 | m_toolWindows << toolWindow; 171 | m_toolWindowProperties[toolWindow] = properties; 172 | QObject::connect(toolWindow, &QWidget::windowTitleChanged, this, 173 | &ToolWindowManager::windowTitleChanged); 174 | } 175 | moveToolWindows(toolWindows, area); 176 | } 177 | 178 | ToolWindowManagerArea *ToolWindowManager::areaOf(QWidget *toolWindow) 179 | { 180 | return findClosestParent(toolWindow); 181 | } 182 | 183 | ToolWindowManagerWrapper *ToolWindowManager::wrapperOf(QWidget *toolWindow) 184 | { 185 | return findClosestParent(toolWindow); 186 | } 187 | 188 | void ToolWindowManager::moveToolWindow(QWidget *toolWindow, AreaReference area) 189 | { 190 | moveToolWindows(QList() << toolWindow, area); 191 | } 192 | 193 | void ToolWindowManager::moveToolWindows(QList toolWindows, 194 | ToolWindowManager::AreaReference area) 195 | { 196 | QList wrappersToUpdate; 197 | foreach(QWidget *toolWindow, toolWindows) 198 | { 199 | if(!m_toolWindows.contains(toolWindow)) 200 | { 201 | qWarning() << "unknown tool window:" << (toolWindow ? toolWindow->objectName() : QString()); 202 | return; 203 | } 204 | ToolWindowManagerWrapper *oldWrapper = wrapperOf(toolWindow); 205 | if(toolWindow->parentWidget() != 0) 206 | { 207 | releaseToolWindow(toolWindow); 208 | } 209 | if(oldWrapper && !wrappersToUpdate.contains(oldWrapper)) 210 | wrappersToUpdate.push_back(oldWrapper); 211 | } 212 | // if we don't have a reference area, we can't use any types that need a reference 213 | if(area.area() == NULL && (area.type() == AddTo || area.type() == LeftOf || area.type() == RightOf || 214 | area.type() == TopOf || area.type() == BottomOf || 215 | area.type() == LeftWindowSide || area.type() == RightWindowSide || 216 | area.type() == TopWindowSide || area.type() == BottomWindowSide)) 217 | 218 | { 219 | // if the last area is available, use that. 220 | if(m_lastUsedArea) 221 | area = AreaReference(AddTo, m_lastUsedArea); 222 | // if we have no tool windows at all, add into empty space 223 | else if(m_toolWindows.isEmpty() || m_toolWindows == toolWindows) 224 | area = AreaReference(EmptySpace); 225 | // otherwise we have to make it a new floating area 226 | else 227 | area = AreaReference(NewFloatingArea); 228 | } 229 | if(area.type() == LastUsedArea && !m_lastUsedArea) 230 | { 231 | ToolWindowManagerArea *foundArea = findChild(); 232 | if(foundArea) 233 | { 234 | area = AreaReference(AddTo, foundArea); 235 | } 236 | else 237 | { 238 | area = EmptySpace; 239 | } 240 | } 241 | 242 | if(area.type() == NoArea) 243 | { 244 | // do nothing 245 | } 246 | else if(area.type() == NewFloatingArea) 247 | { 248 | ToolWindowManagerArea *floatArea = createArea(); 249 | floatArea->addToolWindows(toolWindows); 250 | ToolWindowManagerWrapper *wrapper = new ToolWindowManagerWrapper(this, true); 251 | wrapper->layout()->addWidget(floatArea); 252 | wrapper->move(QCursor::pos()); 253 | wrapper->updateTitle(); 254 | wrapper->show(); 255 | } 256 | else if(area.type() == AddTo) 257 | { 258 | int idx = -1; 259 | if(area.dragResult) 260 | { 261 | idx = area.area()->tabBar()->tabAt(area.area()->tabBar()->mapFromGlobal(QCursor::pos())); 262 | } 263 | area.area()->addToolWindows(toolWindows, idx); 264 | } 265 | else if(area.type() == LeftWindowSide || area.type() == RightWindowSide || 266 | area.type() == TopWindowSide || area.type() == BottomWindowSide) 267 | { 268 | ToolWindowManagerWrapper *wrapper = findClosestParent(area.area()); 269 | if(!wrapper) 270 | { 271 | qWarning("couldn't find wrapper"); 272 | return; 273 | } 274 | 275 | if(wrapper->layout()->count() > 1) 276 | { 277 | qWarning("wrapper has multiple direct children"); 278 | return; 279 | } 280 | 281 | QLayoutItem *item = wrapper->layout()->takeAt(0); 282 | 283 | QSplitter *splitter = createSplitter(); 284 | if(area.type() == TopWindowSide || area.type() == BottomWindowSide) 285 | { 286 | splitter->setOrientation(Qt::Vertical); 287 | } 288 | else 289 | { 290 | splitter->setOrientation(Qt::Horizontal); 291 | } 292 | 293 | splitter->addWidget(item->widget()); 294 | area.widget()->show(); 295 | 296 | delete item; 297 | 298 | ToolWindowManagerArea *newArea = createArea(); 299 | newArea->addToolWindows(toolWindows); 300 | 301 | if(area.type() == TopWindowSide || area.type() == LeftWindowSide) 302 | { 303 | splitter->insertWidget(0, newArea); 304 | } 305 | else 306 | { 307 | splitter->addWidget(newArea); 308 | } 309 | 310 | wrapper->layout()->addWidget(splitter); 311 | 312 | QRect areaGeometry = area.widget()->geometry(); 313 | 314 | // Convert area percentage desired to relative sizes. 315 | const int totalStretch = (area.type() == TopWindowSide || area.type() == BottomWindowSide) 316 | ? areaGeometry.height() 317 | : areaGeometry.width(); 318 | int pct = int(totalStretch * area.percentage()); 319 | 320 | int a = pct; 321 | int b = totalStretch - pct; 322 | 323 | if(area.type() == BottomWindowSide || area.type() == RightWindowSide) 324 | std::swap(a, b); 325 | 326 | splitter->setSizes({a, b}); 327 | } 328 | else if(area.type() == LeftOf || area.type() == RightOf || area.type() == TopOf || 329 | area.type() == BottomOf) 330 | { 331 | QSplitter *parentSplitter = qobject_cast(area.widget()->parentWidget()); 332 | ToolWindowManagerWrapper *wrapper = 333 | qobject_cast(area.widget()->parentWidget()); 334 | if(!parentSplitter && !wrapper) 335 | { 336 | qWarning("unknown parent type"); 337 | return; 338 | } 339 | bool useParentSplitter = false; 340 | int indexInParentSplitter = 0; 341 | QList parentSplitterSizes; 342 | if(parentSplitter) 343 | { 344 | indexInParentSplitter = parentSplitter->indexOf(area.widget()); 345 | parentSplitterSizes = parentSplitter->sizes(); 346 | if(parentSplitter->orientation() == Qt::Vertical) 347 | { 348 | useParentSplitter = area.type() == TopOf || area.type() == BottomOf; 349 | } 350 | else 351 | { 352 | useParentSplitter = area.type() == LeftOf || area.type() == RightOf; 353 | } 354 | } 355 | if(useParentSplitter) 356 | { 357 | int insertIndex = indexInParentSplitter; 358 | if(area.type() == BottomOf || area.type() == RightOf) 359 | { 360 | insertIndex++; 361 | } 362 | ToolWindowManagerArea *newArea = createArea(); 363 | newArea->addToolWindows(toolWindows); 364 | parentSplitter->insertWidget(insertIndex, newArea); 365 | 366 | if(parentSplitterSizes.count() > indexInParentSplitter && parentSplitterSizes[0] != 0) 367 | { 368 | int availSize = parentSplitterSizes[indexInParentSplitter]; 369 | 370 | parentSplitterSizes[indexInParentSplitter] = int(availSize * (1.0f - area.percentage())); 371 | parentSplitterSizes.insert(insertIndex, int(availSize * area.percentage())); 372 | 373 | parentSplitter->setSizes(parentSplitterSizes); 374 | } 375 | } 376 | else 377 | { 378 | area.widget()->hide(); 379 | area.widget()->setParent(0); 380 | QSplitter *splitter = createSplitter(); 381 | if(area.type() == TopOf || area.type() == BottomOf) 382 | { 383 | splitter->setOrientation(Qt::Vertical); 384 | } 385 | else 386 | { 387 | splitter->setOrientation(Qt::Horizontal); 388 | } 389 | 390 | ToolWindowManagerArea *newArea = createArea(); 391 | 392 | // inherit the size policy from the widget we are wrapping 393 | splitter->setSizePolicy(area.widget()->sizePolicy()); 394 | 395 | // store old geometries so we can restore them 396 | QRect areaGeometry = area.widget()->geometry(); 397 | QRect newGeometry = newArea->geometry(); 398 | 399 | splitter->addWidget(area.widget()); 400 | area.widget()->show(); 401 | 402 | if(area.type() == TopOf || area.type() == LeftOf) 403 | { 404 | splitter->insertWidget(0, newArea); 405 | } 406 | else 407 | { 408 | splitter->addWidget(newArea); 409 | } 410 | 411 | if(parentSplitter) 412 | { 413 | parentSplitter->insertWidget(indexInParentSplitter, splitter); 414 | 415 | if(parentSplitterSizes.count() > 0 && parentSplitterSizes[0] != 0) 416 | { 417 | parentSplitter->setSizes(parentSplitterSizes); 418 | } 419 | } 420 | else 421 | { 422 | wrapper->layout()->addWidget(splitter); 423 | } 424 | 425 | newArea->addToolWindows(toolWindows); 426 | 427 | area.widget()->setGeometry(areaGeometry); 428 | newArea->setGeometry(newGeometry); 429 | 430 | // Convert area percentage desired to relative sizes. 431 | const int totalStretch = (area.type() == TopOf || area.type() == BottomOf) 432 | ? areaGeometry.height() 433 | : areaGeometry.width(); 434 | int pct = int(totalStretch * area.percentage()); 435 | 436 | int a = pct; 437 | int b = totalStretch - pct; 438 | 439 | if(area.type() == BottomOf || area.type() == RightOf) 440 | std::swap(a, b); 441 | 442 | splitter->setSizes({a, b}); 443 | } 444 | } 445 | else if(area.type() == EmptySpace) 446 | { 447 | ToolWindowManagerArea *newArea = createArea(); 448 | findChild()->layout()->addWidget(newArea); 449 | newArea->addToolWindows(toolWindows); 450 | } 451 | else if(area.type() == LastUsedArea) 452 | { 453 | m_lastUsedArea->addToolWindows(toolWindows); 454 | } 455 | else 456 | { 457 | qWarning("invalid type"); 458 | } 459 | simplifyLayout(); 460 | foreach(QWidget *toolWindow, toolWindows) 461 | { 462 | emit toolWindowVisibilityChanged(toolWindow, toolWindow->parent() != 0); 463 | ToolWindowManagerWrapper *wrapper = wrapperOf(toolWindow); 464 | if(wrapper && !wrappersToUpdate.contains(wrapper)) 465 | wrappersToUpdate.push_back(wrapper); 466 | } 467 | foreach(ToolWindowManagerWrapper *wrapper, wrappersToUpdate) 468 | { 469 | wrapper->updateTitle(); 470 | } 471 | } 472 | 473 | void ToolWindowManager::removeToolWindow(QWidget *toolWindow, bool allowCloseAlreadyChecked) 474 | { 475 | if(!m_toolWindows.contains(toolWindow)) 476 | { 477 | qWarning() << "unknown tool window:" << (toolWindow ? toolWindow->objectName() : QString()); 478 | return; 479 | } 480 | 481 | // search up to find the first parent manager 482 | ToolWindowManager *manager = findClosestParent(toolWindow); 483 | 484 | if(!manager) 485 | { 486 | qWarning() << "window not child of any tool window" 487 | << (toolWindow ? toolWindow->objectName() : QString()); 488 | return; 489 | } 490 | 491 | if(!allowCloseAlreadyChecked) 492 | { 493 | if(!manager->allowClose(toolWindow)) 494 | return; 495 | } 496 | 497 | forceCloseToolWindow(toolWindow); 498 | } 499 | 500 | void ToolWindowManager::forceCloseToolWindow(QWidget *toolWindow) 501 | { 502 | moveToolWindow(toolWindow, NoArea); 503 | m_toolWindows.removeOne(toolWindow); 504 | m_toolWindowProperties.remove(toolWindow); 505 | delete toolWindow; 506 | } 507 | 508 | bool ToolWindowManager::isFloating(QWidget *toolWindow) 509 | { 510 | ToolWindowManagerWrapper *wrapper = wrapperOf(toolWindow); 511 | if(wrapper) 512 | { 513 | return wrapper->floating(); 514 | } 515 | return false; 516 | } 517 | 518 | ToolWindowManager *ToolWindowManager::managerOf(QWidget *toolWindow) 519 | { 520 | if(!toolWindow) 521 | { 522 | qWarning("NULL tool window"); 523 | return NULL; 524 | } 525 | 526 | return findClosestParent(toolWindow); 527 | } 528 | 529 | void ToolWindowManager::closeToolWindow(QWidget *toolWindow) 530 | { 531 | if(!toolWindow) 532 | { 533 | qWarning("NULL tool window"); 534 | return; 535 | } 536 | 537 | // search up to find the first parent manager 538 | ToolWindowManager *manager = findClosestParent(toolWindow); 539 | 540 | if(manager) 541 | { 542 | manager->removeToolWindow(toolWindow); 543 | return; 544 | } 545 | 546 | qWarning() << "window not child of any tool window" << toolWindow->objectName(); 547 | } 548 | 549 | void ToolWindowManager::raiseToolWindow(QWidget *toolWindow) 550 | { 551 | if(!toolWindow) 552 | { 553 | qWarning("NULL tool window"); 554 | return; 555 | } 556 | 557 | // if the parent is a ToolWindowManagerArea, switch tabs 558 | QWidget *parent = toolWindow->parentWidget(); 559 | ToolWindowManagerArea *area = qobject_cast(parent); 560 | if(area == NULL && parent) 561 | parent = parent->parentWidget(); 562 | 563 | area = qobject_cast(parent); 564 | 565 | if(area) 566 | area->setCurrentWidget(toolWindow); 567 | else 568 | qWarning() << "parent is not a tool window area" << toolWindow->objectName(); 569 | } 570 | 571 | QWidget *ToolWindowManager::createToolWindow(const QString &objectName) 572 | { 573 | if(m_createCallback) 574 | { 575 | QWidget *toolWindow = m_createCallback(objectName); 576 | if(toolWindow) 577 | { 578 | m_toolWindows << toolWindow; 579 | m_toolWindowProperties[toolWindow] = ToolWindowProperty(0); 580 | QObject::connect(toolWindow, &QWidget::windowTitleChanged, this, 581 | &ToolWindowManager::windowTitleChanged); 582 | return toolWindow; 583 | } 584 | } 585 | 586 | return NULL; 587 | } 588 | 589 | void ToolWindowManager::setDropHotspotMargin(int pixels) 590 | { 591 | m_dropHotspotMargin = pixels; 592 | drawHotspotPixmaps(); 593 | } 594 | 595 | void ToolWindowManager::setDropHotspotDimension(int pixels) 596 | { 597 | m_dropHotspotDimension = pixels; 598 | 599 | for(QLabel *hotspot : m_dropHotspots) 600 | { 601 | if(hotspot) 602 | hotspot->setFixedSize(m_dropHotspotDimension, m_dropHotspotDimension); 603 | } 604 | } 605 | 606 | void ToolWindowManager::setAllowFloatingWindow(bool allow) 607 | { 608 | m_allowFloatingWindow = allow; 609 | } 610 | 611 | QVariantMap ToolWindowManager::saveState() 612 | { 613 | QVariantMap result; 614 | result[QStringLiteral("toolWindowManagerStateFormat")] = 1; 615 | ToolWindowManagerWrapper *mainWrapper = findChild(); 616 | if(!mainWrapper) 617 | { 618 | qWarning("can't find main wrapper"); 619 | return QVariantMap(); 620 | } 621 | result[QStringLiteral("mainWrapper")] = mainWrapper->saveState(); 622 | QVariantList floatingWindowsData; 623 | foreach(ToolWindowManagerWrapper *wrapper, m_wrappers) 624 | { 625 | if(!wrapper->isWindow()) 626 | { 627 | continue; 628 | } 629 | floatingWindowsData << wrapper->saveState(); 630 | } 631 | result[QStringLiteral("floatingWindows")] = floatingWindowsData; 632 | return result; 633 | } 634 | 635 | void ToolWindowManager::restoreState(const QVariantMap &dataMap) 636 | { 637 | if(dataMap.isEmpty()) 638 | { 639 | return; 640 | } 641 | if(dataMap[QStringLiteral("toolWindowManagerStateFormat")].toInt() != 1) 642 | { 643 | qWarning("state format is not recognized"); 644 | return; 645 | } 646 | moveToolWindows(m_toolWindows, NoArea); 647 | ToolWindowManagerWrapper *mainWrapper = findChild(); 648 | if(!mainWrapper) 649 | { 650 | qWarning("can't find main wrapper"); 651 | return; 652 | } 653 | mainWrapper->restoreState(dataMap[QStringLiteral("mainWrapper")].toMap()); 654 | QVariantList floatWins = dataMap[QStringLiteral("floatingWindows")].toList(); 655 | foreach(QVariant windowData, floatWins) 656 | { 657 | QVariantMap floatData = windowData.toMap(); 658 | if(floatData.empty()) 659 | continue; 660 | 661 | ToolWindowManagerWrapper *wrapper = new ToolWindowManagerWrapper(this, true); 662 | wrapper->restoreState(floatData); 663 | wrapper->updateTitle(); 664 | wrapper->show(); 665 | if(wrapper->windowState() & Qt::WindowMaximized) 666 | { 667 | wrapper->setWindowState(0); 668 | wrapper->setWindowState(Qt::WindowMaximized); 669 | } 670 | } 671 | simplifyLayout(); 672 | foreach(QWidget *toolWindow, m_toolWindows) 673 | { 674 | emit toolWindowVisibilityChanged(toolWindow, toolWindow->parentWidget() != 0); 675 | } 676 | } 677 | 678 | ToolWindowManagerArea *ToolWindowManager::createArea() 679 | { 680 | ToolWindowManagerArea *area = new ToolWindowManagerArea(this, 0); 681 | connect(area, SIGNAL(tabCloseRequested(int)), this, SLOT(tabCloseRequested(int))); 682 | return area; 683 | } 684 | 685 | void ToolWindowManager::releaseToolWindow(QWidget *toolWindow) 686 | { 687 | ToolWindowManagerArea *previousTabWidget = findClosestParent(toolWindow); 688 | if(!previousTabWidget) 689 | { 690 | qWarning() << "cannot find tab widget for tool window:" 691 | << (toolWindow ? toolWindow->objectName() : QString()); 692 | return; 693 | } 694 | previousTabWidget->removeTab(previousTabWidget->indexOf(toolWindow)); 695 | toolWindow->hide(); 696 | toolWindow->setParent(0); 697 | } 698 | 699 | void ToolWindowManager::simplifyLayout() 700 | { 701 | foreach(ToolWindowManagerArea *area, m_areas) 702 | { 703 | if(area->parentWidget() == 0) 704 | { 705 | if(area->count() == 0) 706 | { 707 | if(area == m_lastUsedArea) 708 | { 709 | m_lastUsedArea = 0; 710 | } 711 | // QTimer::singleShot(1000, area, SLOT(deleteLater())); 712 | area->deleteLater(); 713 | } 714 | continue; 715 | } 716 | QSplitter *splitter = qobject_cast(area->parentWidget()); 717 | QSplitter *validSplitter = 0; // least top level splitter that should remain 718 | QSplitter *invalidSplitter = 0; // most top level splitter that should be deleted 719 | while(splitter) 720 | { 721 | if(splitter->count() > 1) 722 | { 723 | validSplitter = splitter; 724 | break; 725 | } 726 | else 727 | { 728 | invalidSplitter = splitter; 729 | splitter = qobject_cast(splitter->parentWidget()); 730 | } 731 | } 732 | if(!validSplitter) 733 | { 734 | ToolWindowManagerWrapper *wrapper = findClosestParent(area); 735 | if(!wrapper) 736 | { 737 | qWarning("can't find wrapper"); 738 | return; 739 | } 740 | if(area->count() == 0 && wrapper->isWindow()) 741 | { 742 | wrapper->hide(); 743 | // can't deleteLater immediately (strange MacOS bug) 744 | // QTimer::singleShot(1000, wrapper, SLOT(deleteLater())); 745 | wrapper->deleteLater(); 746 | } 747 | else if(area->parent() != wrapper) 748 | { 749 | wrapper->layout()->addWidget(area); 750 | } 751 | } 752 | else 753 | { 754 | if(area->count() > 0) 755 | { 756 | if(validSplitter && area->parent() != validSplitter) 757 | { 758 | int index = validSplitter->indexOf(invalidSplitter); 759 | validSplitter->insertWidget(index, area); 760 | } 761 | } 762 | } 763 | if(invalidSplitter) 764 | { 765 | invalidSplitter->hide(); 766 | invalidSplitter->setParent(0); 767 | // QTimer::singleShot(1000, invalidSplitter, SLOT(deleteLater())); 768 | invalidSplitter->deleteLater(); 769 | } 770 | if(area->count() == 0) 771 | { 772 | area->hide(); 773 | area->setParent(0); 774 | if(area == m_lastUsedArea) 775 | { 776 | m_lastUsedArea = 0; 777 | } 778 | // QTimer::singleShot(1000, area, SLOT(deleteLater())); 779 | area->deleteLater(); 780 | } 781 | // search up the stack looking for splitters that have only one child which is a splitter 782 | splitter = qobject_cast(area->parentWidget()); 783 | QSplitter *parentSplitter = splitter ? qobject_cast(splitter->parentWidget()) : NULL; 784 | while(splitter && parentSplitter) 785 | { 786 | // this splitter has only one child, and its direct parent is a splitter. Move our child 787 | // widget 788 | // into the parent and delete. 789 | if(splitter->count() == 1) 790 | { 791 | int idx = parentSplitter->indexOf(splitter); 792 | if(idx == -1) 793 | { 794 | qCritical() << "Couldn't find splitter in parent widget"; 795 | break; 796 | } 797 | 798 | QWidget *child = splitter->widget(0); 799 | 800 | parentSplitter->insertWidget(idx, child); 801 | child->show(); 802 | 803 | splitter->setParent(NULL); 804 | splitter->hide(); 805 | splitter->deleteLater(); 806 | } 807 | 808 | // move up the stack 809 | splitter = parentSplitter; 810 | parentSplitter = qobject_cast(splitter->parentWidget()); 811 | } 812 | } 813 | } 814 | 815 | void ToolWindowManager::startDrag(const QList &toolWindows, 816 | ToolWindowManagerWrapper *wrapper) 817 | { 818 | if(dragInProgress()) 819 | { 820 | qWarning("ToolWindowManager::execDrag: drag is already in progress"); 821 | return; 822 | } 823 | foreach(QWidget *toolWindow, toolWindows) 824 | { 825 | if(toolWindowProperties(toolWindow) & DisallowUserDocking) 826 | { 827 | return; 828 | } 829 | } 830 | if(toolWindows.isEmpty()) 831 | { 832 | return; 833 | } 834 | 835 | m_draggedWrapper = wrapper; 836 | m_draggedToolWindows.clear(); 837 | for(QWidget *w : toolWindows) 838 | m_draggedToolWindows.push_back(w); 839 | qApp->installEventFilter(this); 840 | } 841 | 842 | QVariantMap ToolWindowManager::saveSplitterState(QSplitter *splitter) 843 | { 844 | QVariantMap result; 845 | result[QStringLiteral("state")] = QString::fromLatin1(splitter->saveState().toBase64()); 846 | result[QStringLiteral("type")] = QStringLiteral("splitter"); 847 | QVariantList items; 848 | for(int i = 0; i < splitter->count(); i++) 849 | { 850 | QWidget *item = splitter->widget(i); 851 | QVariantMap itemValue; 852 | ToolWindowManagerArea *area = qobject_cast(item); 853 | if(area) 854 | { 855 | itemValue = area->saveState(); 856 | } 857 | else 858 | { 859 | QSplitter *childSplitter = qobject_cast(item); 860 | if(childSplitter) 861 | { 862 | itemValue = saveSplitterState(childSplitter); 863 | } 864 | else 865 | { 866 | qWarning("unknown splitter item"); 867 | } 868 | } 869 | items << itemValue; 870 | } 871 | result[QStringLiteral("items")] = items; 872 | return result; 873 | } 874 | 875 | QSplitter *ToolWindowManager::restoreSplitterState(const QVariantMap &savedData) 876 | { 877 | if(savedData[QStringLiteral("items")].toList().count() < 2) 878 | { 879 | qWarning("invalid splitter encountered"); 880 | } 881 | QSplitter *splitter = createSplitter(); 882 | 883 | QVariantList itemList = savedData[QStringLiteral("items")].toList(); 884 | foreach(QVariant itemData, itemList) 885 | { 886 | QVariantMap itemValue = itemData.toMap(); 887 | QString itemType = itemValue[QStringLiteral("type")].toString(); 888 | if(itemType == QStringLiteral("splitter")) 889 | { 890 | splitter->addWidget(restoreSplitterState(itemValue)); 891 | } 892 | else if(itemType == QStringLiteral("area")) 893 | { 894 | ToolWindowManagerArea *area = createArea(); 895 | area->restoreState(itemValue); 896 | splitter->addWidget(area); 897 | } 898 | else 899 | { 900 | qWarning("unknown item type"); 901 | } 902 | } 903 | splitter->restoreState(QByteArray::fromBase64(savedData[QStringLiteral("state")].toByteArray())); 904 | return splitter; 905 | } 906 | 907 | void ToolWindowManager::updateDragPosition() 908 | { 909 | if(!dragInProgress()) 910 | { 911 | return; 912 | } 913 | if(!(qApp->mouseButtons() & Qt::LeftButton)) 914 | { 915 | finishDrag(); 916 | return; 917 | } 918 | 919 | QPoint pos = QCursor::pos(); 920 | m_hoverArea = NULL; 921 | ToolWindowManagerWrapper *hoverWrapper = NULL; 922 | 923 | foreach(ToolWindowManagerArea *area, m_areas) 924 | { 925 | // don't allow dragging a whole wrapper into a subset of itself 926 | if(m_draggedWrapper && area->window() == m_draggedWrapper->window()) 927 | { 928 | continue; 929 | } 930 | QRect globalAreaRect(area->mapToGlobal(area->rect().topLeft()), 931 | area->mapToGlobal(area->rect().bottomRight())); 932 | if(globalAreaRect.contains(pos)) 933 | { 934 | m_hoverArea = area; 935 | break; 936 | } 937 | } 938 | 939 | if(m_hoverArea == NULL) 940 | { 941 | foreach(ToolWindowManagerWrapper *wrapper, m_wrappers) 942 | { 943 | // don't allow dragging a whole wrapper into a subset of itself 944 | if(wrapper == m_draggedWrapper) 945 | { 946 | continue; 947 | } 948 | if(wrapper->rect().contains(wrapper->mapFromGlobal(pos))) 949 | { 950 | hoverWrapper = wrapper; 951 | break; 952 | } 953 | } 954 | 955 | // if we found a wrapper and it's not empty, then we fill into a gap between two areas in a 956 | // splitter. Search down the hierarchy until we find a splitter whose handle intersects the 957 | // cursor and pick an area to map to. 958 | if(hoverWrapper) 959 | { 960 | QLayout *layout = hoverWrapper->layout(); 961 | QLayoutItem *layoutitem = layout ? layout->itemAt(0) : NULL; 962 | QWidget *layoutwidget = layoutitem ? layoutitem->widget() : NULL; 963 | QSplitter *splitter = qobject_cast(layoutwidget); 964 | 965 | while(splitter) 966 | { 967 | QSplitter *previous = splitter; 968 | 969 | for(int h = 1; h < splitter->count(); h++) 970 | { 971 | QSplitterHandle *handle = splitter->handle(h); 972 | 973 | if(handle->rect().contains(handle->mapFromGlobal(pos))) 974 | { 975 | QWidget *a = splitter->widget(h); 976 | QWidget *b = splitter->widget(h + 1); 977 | 978 | // try the first widget, if it's an area stop 979 | m_hoverArea = qobject_cast(a); 980 | if(m_hoverArea) 981 | break; 982 | 983 | // then the second widget 984 | m_hoverArea = qobject_cast(b); 985 | if(m_hoverArea) 986 | break; 987 | 988 | // neither widget is an area - let's search for a splitter to recurse to 989 | splitter = qobject_cast(a); 990 | if(splitter) 991 | break; 992 | 993 | splitter = qobject_cast(b); 994 | if(splitter) 995 | break; 996 | 997 | // neither side is an area or a splitter - should be impossible, but stop recursing 998 | // and treat this like a floating window 999 | qWarning("Couldn't find splitter or area at terminal side of splitter"); 1000 | splitter = NULL; 1001 | hoverWrapper = NULL; 1002 | break; 1003 | } 1004 | } 1005 | 1006 | // if we still have a splitter, and didn't find an area, find which widget contains the 1007 | // cursor and recurse to that splitter 1008 | if(previous == splitter && !m_hoverArea) 1009 | { 1010 | for(int w = 0; w < splitter->count(); w++) 1011 | { 1012 | QWidget *widget = splitter->widget(w); 1013 | 1014 | if(widget->rect().contains(widget->mapFromGlobal(pos))) 1015 | { 1016 | splitter = qobject_cast(widget); 1017 | if(splitter) 1018 | break; 1019 | 1020 | // if this isn't a splitter, and it's not an area (since that would have been found 1021 | // before any of this started) then bail out 1022 | qWarning("cursor inside unknown child widget that isn't a splitter or area"); 1023 | splitter = NULL; 1024 | hoverWrapper = NULL; 1025 | break; 1026 | } 1027 | } 1028 | } 1029 | 1030 | // we found an area to use! stop now 1031 | if(m_hoverArea) 1032 | break; 1033 | 1034 | // if we still haven't found anything, bail out 1035 | if(previous == splitter) 1036 | { 1037 | qWarning("Couldn't find cursor inside any child of wrapper"); 1038 | splitter = NULL; 1039 | hoverWrapper = NULL; 1040 | break; 1041 | } 1042 | } 1043 | } 1044 | } 1045 | 1046 | if(m_hoverArea || hoverWrapper) 1047 | { 1048 | ToolWindowManagerWrapper *wrapper = hoverWrapper; 1049 | if(m_hoverArea) 1050 | wrapper = findClosestParent(m_hoverArea); 1051 | QRect wrapperGeometry; 1052 | wrapperGeometry.setSize(wrapper->rect().size()); 1053 | wrapperGeometry.moveTo(wrapper->mapToGlobal(QPoint(0, 0))); 1054 | 1055 | const int margin = m_dropHotspotMargin; 1056 | 1057 | const int size = m_dropHotspotDimension; 1058 | const int hsize = size / 2; 1059 | 1060 | if(m_hoverArea) 1061 | { 1062 | QRect areaClientRect; 1063 | 1064 | // calculate the rect of the area 1065 | areaClientRect.setTopLeft(m_hoverArea->mapToGlobal(QPoint(0, 0))); 1066 | areaClientRect.setSize(m_hoverArea->rect().size()); 1067 | 1068 | // subtract the rect for the tab bar. 1069 | areaClientRect.adjust(0, m_hoverArea->tabBar()->rect().height(), 0, 0); 1070 | 1071 | QPoint c = areaClientRect.center(); 1072 | 1073 | if(m_hoverArea->allowUserDrop()) 1074 | { 1075 | m_dropHotspots[AddTo]->move(c + QPoint(-hsize, -hsize)); 1076 | m_dropHotspots[AddTo]->show(); 1077 | } 1078 | else 1079 | { 1080 | m_dropHotspots[AddTo]->hide(); 1081 | } 1082 | 1083 | m_dropHotspots[TopOf]->move(c + QPoint(-hsize, -hsize - margin - size)); 1084 | m_dropHotspots[TopOf]->show(); 1085 | 1086 | m_dropHotspots[LeftOf]->move(c + QPoint(-hsize - margin - size, -hsize)); 1087 | m_dropHotspots[LeftOf]->show(); 1088 | 1089 | m_dropHotspots[RightOf]->move(c + QPoint(hsize + margin, -hsize)); 1090 | m_dropHotspots[RightOf]->show(); 1091 | 1092 | m_dropHotspots[BottomOf]->move(c + QPoint(-hsize, hsize + margin)); 1093 | m_dropHotspots[BottomOf]->show(); 1094 | 1095 | c = wrapperGeometry.center(); 1096 | 1097 | m_dropHotspots[TopWindowSide]->move(QPoint(c.x() - hsize, wrapperGeometry.y() + margin * 2)); 1098 | m_dropHotspots[TopWindowSide]->show(); 1099 | 1100 | m_dropHotspots[LeftWindowSide]->move(QPoint(wrapperGeometry.x() + margin * 2, c.y() - hsize)); 1101 | m_dropHotspots[LeftWindowSide]->show(); 1102 | 1103 | m_dropHotspots[RightWindowSide]->move( 1104 | QPoint(wrapperGeometry.right() - size - margin * 2, c.y() - hsize)); 1105 | m_dropHotspots[RightWindowSide]->show(); 1106 | 1107 | m_dropHotspots[BottomWindowSide]->move( 1108 | QPoint(c.x() - hsize, wrapperGeometry.bottom() - size - margin * 2)); 1109 | m_dropHotspots[BottomWindowSide]->show(); 1110 | } 1111 | else 1112 | { 1113 | m_dropHotspots[AddTo]->move(wrapperGeometry.center() + QPoint(-hsize, -hsize)); 1114 | m_dropHotspots[AddTo]->show(); 1115 | 1116 | m_dropHotspots[TopOf]->hide(); 1117 | m_dropHotspots[LeftOf]->hide(); 1118 | m_dropHotspots[RightOf]->hide(); 1119 | m_dropHotspots[BottomOf]->hide(); 1120 | 1121 | m_dropHotspots[TopWindowSide]->hide(); 1122 | m_dropHotspots[LeftWindowSide]->hide(); 1123 | m_dropHotspots[RightWindowSide]->hide(); 1124 | m_dropHotspots[BottomWindowSide]->hide(); 1125 | } 1126 | } 1127 | else 1128 | { 1129 | for(QWidget *hotspot : m_dropHotspots) 1130 | if(hotspot) 1131 | hotspot->hide(); 1132 | } 1133 | 1134 | AreaReferenceType hotspot = currentHotspot(); 1135 | if((m_hoverArea || hoverWrapper) && (hotspot == AddTo || hotspot == LeftOf || hotspot == RightOf || 1136 | hotspot == TopOf || hotspot == BottomOf)) 1137 | { 1138 | QWidget *parent = m_hoverArea; 1139 | if(parent == NULL) 1140 | parent = hoverWrapper; 1141 | 1142 | QRect g = parent->geometry(); 1143 | g.moveTopLeft(parent->parentWidget()->mapToGlobal(g.topLeft())); 1144 | 1145 | if(hotspot == LeftOf) 1146 | g.adjust(0, 0, -g.width() / 2, 0); 1147 | else if(hotspot == RightOf) 1148 | g.adjust(g.width() / 2, 0, 0, 0); 1149 | else if(hotspot == TopOf) 1150 | g.adjust(0, 0, 0, -g.height() / 2); 1151 | else if(hotspot == BottomOf) 1152 | g.adjust(0, g.height() / 2, 0, 0); 1153 | 1154 | QRect tabGeom; 1155 | 1156 | if(hotspot == AddTo && m_hoverArea && m_hoverArea->count() > 1) 1157 | { 1158 | QTabBar *tb = m_hoverArea->tabBar(); 1159 | g.adjust(0, tb->rect().height(), 0, 0); 1160 | 1161 | int idx = tb->tabAt(tb->mapFromGlobal(pos)); 1162 | 1163 | if(idx == -1) 1164 | { 1165 | tabGeom = tb->tabRect(m_hoverArea->count() - 1); 1166 | tabGeom.moveTo(tb->mapToGlobal(QPoint(0, 0)) + tabGeom.topLeft()); 1167 | 1168 | // move the tab one to the right, to indicate the tab is being added after the last one. 1169 | tabGeom.moveLeft(tabGeom.left() + tabGeom.width()); 1170 | 1171 | // clamp from the right, to ensure we don't display any tab off the end of the range 1172 | if(tabGeom.right() > g.right()) 1173 | tabGeom.moveLeft(g.right() - tabGeom.width()); 1174 | } 1175 | else 1176 | { 1177 | tabGeom = tb->tabRect(idx); 1178 | tabGeom.moveTo(tb->mapToGlobal(QPoint(0, 0)) + tabGeom.topLeft()); 1179 | } 1180 | } 1181 | 1182 | m_previewOverlay->setGeometry(g); 1183 | 1184 | m_previewTabOverlay->setGeometry(tabGeom); 1185 | } 1186 | else if((m_hoverArea || hoverWrapper) && (hotspot == LeftWindowSide || hotspot == RightWindowSide || 1187 | hotspot == TopWindowSide || hotspot == BottomWindowSide)) 1188 | { 1189 | ToolWindowManagerWrapper *wrapper = hoverWrapper; 1190 | if(m_hoverArea) 1191 | wrapper = findClosestParent(m_hoverArea); 1192 | 1193 | QRect g; 1194 | g.moveTopLeft(wrapper->mapToGlobal(QPoint())); 1195 | g.setSize(wrapper->rect().size()); 1196 | 1197 | if(hotspot == LeftWindowSide) 1198 | g.adjust(0, 0, -(g.width() * 5) / 6, 0); 1199 | else if(hotspot == RightWindowSide) 1200 | g.adjust((g.width() * 5) / 6, 0, 0, 0); 1201 | else if(hotspot == TopWindowSide) 1202 | g.adjust(0, 0, 0, -(g.height() * 3) / 4); 1203 | else if(hotspot == BottomWindowSide) 1204 | g.adjust(0, (g.height() * 3) / 4, 0, 0); 1205 | 1206 | m_previewOverlay->setGeometry(g); 1207 | m_previewTabOverlay->setGeometry(QRect()); 1208 | } 1209 | else 1210 | { 1211 | bool allowFloat = m_allowFloatingWindow; 1212 | 1213 | for(QPointer w : m_draggedToolWindows) 1214 | if(w) 1215 | allowFloat &= !(toolWindowProperties(w) & DisallowFloatWindow); 1216 | 1217 | // no hotspot highlighted, draw geometry for a float window if previewing a tear-off, or draw 1218 | // nothing if we're dragging a float window as it moves itself. 1219 | // we also don't render any preview tear-off when floating windows are disallowed 1220 | if(m_draggedWrapper || !allowFloat) 1221 | { 1222 | m_previewOverlay->setGeometry(QRect()); 1223 | } 1224 | else 1225 | { 1226 | QRect r; 1227 | for(QPointer w : m_draggedToolWindows) 1228 | { 1229 | if(w && w->isVisible()) 1230 | r = r.united(w->rect()); 1231 | } 1232 | m_previewOverlay->setGeometry(pos.x(), pos.y(), r.width(), r.height()); 1233 | } 1234 | m_previewTabOverlay->setGeometry(QRect()); 1235 | } 1236 | 1237 | m_previewOverlay->show(); 1238 | m_previewTabOverlay->show(); 1239 | for(QWidget *h : m_dropHotspots) 1240 | if(h && h->isVisible()) 1241 | h->raise(); 1242 | } 1243 | 1244 | void ToolWindowManager::abortDrag() 1245 | { 1246 | if(!dragInProgress()) 1247 | return; 1248 | 1249 | m_previewOverlay->hide(); 1250 | m_previewTabOverlay->hide(); 1251 | for(QWidget *hotspot : m_dropHotspots) 1252 | if(hotspot) 1253 | hotspot->hide(); 1254 | m_draggedToolWindows.clear(); 1255 | m_draggedWrapper = NULL; 1256 | qApp->removeEventFilter(this); 1257 | } 1258 | 1259 | void ToolWindowManager::finishDrag() 1260 | { 1261 | if(!dragInProgress()) 1262 | { 1263 | qWarning("unexpected finishDrag"); 1264 | return; 1265 | } 1266 | qApp->removeEventFilter(this); 1267 | 1268 | // move these locally to prevent re-entrancy 1269 | QList draggedToolWindows; 1270 | ToolWindowManagerWrapper *draggedWrapper = m_draggedWrapper; 1271 | 1272 | for(QPointer w : m_draggedToolWindows) 1273 | if(w) 1274 | draggedToolWindows.push_back(w); 1275 | 1276 | if(m_draggedToolWindows.size() != draggedToolWindows.size()) 1277 | { 1278 | qWarning("Some dragged windows were all deleted before finishDrag: %d -> %d", 1279 | (int)m_draggedToolWindows.size(), (int)draggedToolWindows.size()); 1280 | 1281 | if(draggedToolWindows.empty()) 1282 | return; 1283 | } 1284 | 1285 | m_draggedToolWindows.clear(); 1286 | m_draggedWrapper = NULL; 1287 | 1288 | AreaReferenceType hotspot = currentHotspot(); 1289 | 1290 | m_previewOverlay->hide(); 1291 | m_previewTabOverlay->hide(); 1292 | for(QWidget *h : m_dropHotspots) 1293 | if(h) 1294 | h->hide(); 1295 | 1296 | if(hotspot == NewFloatingArea) 1297 | { 1298 | // check if we're dragging a whole float window, if so we don't do anything as it's already 1299 | // moved 1300 | if(!draggedWrapper) 1301 | { 1302 | bool allowFloat = m_allowFloatingWindow; 1303 | 1304 | for(QWidget *w : draggedToolWindows) 1305 | allowFloat &= !(toolWindowProperties(w) & DisallowFloatWindow); 1306 | 1307 | if(allowFloat) 1308 | { 1309 | QRect r; 1310 | for(QWidget *w : draggedToolWindows) 1311 | { 1312 | if(w->isVisible()) 1313 | r = r.united(w->rect()); 1314 | } 1315 | 1316 | moveToolWindows(draggedToolWindows, NewFloatingArea); 1317 | 1318 | ToolWindowManagerArea *area = areaOf(draggedToolWindows[0]); 1319 | 1320 | area->parentWidget()->resize(r.size()); 1321 | } 1322 | } 1323 | } 1324 | else 1325 | { 1326 | if(m_hoverArea) 1327 | { 1328 | if(m_hoverArea->allowUserDrop() || hotspot != AreaReferenceType::AddTo) 1329 | { 1330 | AreaReference ref(hotspot, m_hoverArea); 1331 | ref.dragResult = true; 1332 | moveToolWindows(draggedToolWindows, ref); 1333 | } 1334 | } 1335 | else 1336 | { 1337 | moveToolWindows(draggedToolWindows, AreaReference(EmptySpace)); 1338 | } 1339 | } 1340 | } 1341 | 1342 | void ToolWindowManager::drawHotspotPixmaps() 1343 | { 1344 | for(AreaReferenceType ref : {AddTo, LeftOf, TopOf, RightOf, BottomOf}) 1345 | { 1346 | m_pixmaps[ref] = QPixmap(m_dropHotspotDimension * devicePixelRatio(), 1347 | m_dropHotspotDimension * devicePixelRatio()); 1348 | m_pixmaps[ref].setDevicePixelRatio(devicePixelRatioF()); 1349 | 1350 | QPainter p(&m_pixmaps[ref]); 1351 | p.setCompositionMode(QPainter::CompositionMode_Source); 1352 | p.setRenderHint(QPainter::Antialiasing); 1353 | p.setRenderHint(QPainter::HighQualityAntialiasing); 1354 | 1355 | QRectF rect(0, 0, m_dropHotspotDimension, m_dropHotspotDimension); 1356 | 1357 | p.fillRect(rect, Qt::transparent); 1358 | 1359 | rect = rect.marginsAdded(QMarginsF(-1, -1, -1, -1)); 1360 | 1361 | p.setPen(QPen(QBrush(Qt::darkGray), 1.5)); 1362 | p.setBrush(QBrush(Qt::lightGray)); 1363 | p.drawRoundedRect(rect, 1.5, 1.5, Qt::AbsoluteSize); 1364 | 1365 | rect = rect.marginsAdded(QMarginsF(-4, -4, -4, -4)); 1366 | 1367 | QRectF fullRect = rect; 1368 | 1369 | if(ref == LeftOf) 1370 | rect = rect.marginsAdded(QMarginsF(0, 0, -12, 0)); 1371 | else if(ref == TopOf) 1372 | rect = rect.marginsAdded(QMarginsF(0, 0, 0, -12)); 1373 | else if(ref == RightOf) 1374 | rect = rect.marginsAdded(QMarginsF(-12, 0, 0, 0)); 1375 | else if(ref == BottomOf) 1376 | rect = rect.marginsAdded(QMarginsF(0, -12, 0, 0)); 1377 | 1378 | p.setPen(QPen(QBrush(Qt::black), 1.0)); 1379 | p.setBrush(QBrush(Qt::white)); 1380 | p.drawRect(rect); 1381 | 1382 | // add a little title bar 1383 | rect.setHeight(3); 1384 | p.fillRect(rect, Qt::SolidPattern); 1385 | 1386 | // for the sides, add an arrow. 1387 | if(ref != AddTo) 1388 | { 1389 | QPainterPath path; 1390 | 1391 | if(ref == LeftOf) 1392 | { 1393 | QPointF tip = fullRect.center() + QPointF(4, 0); 1394 | 1395 | path.addPolygon(QPolygonF({ 1396 | tip, 1397 | tip + QPoint(3, 3), 1398 | tip + QPoint(3, -3), 1399 | })); 1400 | } 1401 | else if(ref == TopOf) 1402 | { 1403 | QPointF tip = fullRect.center() + QPointF(0, 4); 1404 | 1405 | path.addPolygon(QPolygonF({ 1406 | tip, 1407 | tip + QPointF(-3, 3), 1408 | tip + QPointF(3, 3), 1409 | })); 1410 | } 1411 | else if(ref == RightOf) 1412 | { 1413 | QPointF tip = fullRect.center() + QPointF(-4, 0); 1414 | 1415 | path.addPolygon(QPolygonF({ 1416 | tip, 1417 | tip + QPointF(-3, 3), 1418 | tip + QPointF(-3, -3), 1419 | })); 1420 | } 1421 | else if(ref == BottomOf) 1422 | { 1423 | QPointF tip = fullRect.center() + QPointF(0, -4); 1424 | 1425 | path.addPolygon(QPolygonF({ 1426 | tip, 1427 | tip + QPointF(-3, -3), 1428 | tip + QPointF(3, -3), 1429 | })); 1430 | } 1431 | 1432 | p.fillPath(path, QBrush(Qt::black)); 1433 | } 1434 | } 1435 | 1436 | // duplicate these pixmaps by default 1437 | m_pixmaps[LeftWindowSide] = m_pixmaps[LeftOf]; 1438 | m_pixmaps[RightWindowSide] = m_pixmaps[RightOf]; 1439 | m_pixmaps[TopWindowSide] = m_pixmaps[TopOf]; 1440 | m_pixmaps[BottomWindowSide] = m_pixmaps[BottomOf]; 1441 | } 1442 | 1443 | ToolWindowManager::AreaReferenceType ToolWindowManager::currentHotspot() 1444 | { 1445 | QPoint pos = QCursor::pos(); 1446 | 1447 | for(int i = 0; i < NumReferenceTypes; i++) 1448 | { 1449 | if(m_dropHotspots[i] && m_dropHotspots[i]->isVisible() && 1450 | m_dropHotspots[i]->geometry().contains(pos)) 1451 | { 1452 | return (ToolWindowManager::AreaReferenceType)i; 1453 | } 1454 | } 1455 | 1456 | if(m_hoverArea) 1457 | { 1458 | QTabBar *tb = m_hoverArea->tabBar(); 1459 | if(tb->rect().contains(tb->mapFromGlobal(QCursor::pos()))) 1460 | return AddTo; 1461 | } 1462 | 1463 | return NewFloatingArea; 1464 | } 1465 | 1466 | bool ToolWindowManager::eventFilter(QObject *object, QEvent *event) 1467 | { 1468 | if(event->type() == QEvent::MouseButtonRelease) 1469 | { 1470 | // right clicking aborts any drag in progress 1471 | if(static_cast(event)->button() == Qt::RightButton) 1472 | abortDrag(); 1473 | } 1474 | else if(event->type() == QEvent::KeyPress) 1475 | { 1476 | // pressing escape any drag in progress 1477 | QKeyEvent *ke = (QKeyEvent *)event; 1478 | if(ke->key() == Qt::Key_Escape) 1479 | { 1480 | abortDrag(); 1481 | } 1482 | } 1483 | return QWidget::eventFilter(object, event); 1484 | } 1485 | 1486 | bool ToolWindowManager::allowClose(QWidget *toolWindow) 1487 | { 1488 | if(!m_toolWindows.contains(toolWindow)) 1489 | { 1490 | qWarning() << "unknown tool window:" << (toolWindow ? toolWindow->objectName() : QString()); 1491 | return true; 1492 | } 1493 | int methodIndex = toolWindow->metaObject()->indexOfMethod( 1494 | QMetaObject::normalizedSignature("checkAllowClose()")); 1495 | 1496 | if(methodIndex >= 0) 1497 | { 1498 | bool ret = true; 1499 | toolWindow->metaObject() 1500 | ->method(methodIndex) 1501 | .invoke(toolWindow, Qt::DirectConnection, Q_RETURN_ARG(bool, ret)); 1502 | 1503 | return ret; 1504 | } 1505 | 1506 | return true; 1507 | } 1508 | 1509 | void ToolWindowManager::tabCloseRequested(int index) 1510 | { 1511 | ToolWindowManagerArea *tabWidget = qobject_cast(sender()); 1512 | if(!tabWidget) 1513 | { 1514 | qWarning("sender is not a ToolWindowManagerArea"); 1515 | return; 1516 | } 1517 | QWidget *toolWindow = tabWidget->widget(index); 1518 | if(!m_toolWindows.contains(toolWindow)) 1519 | { 1520 | qWarning("unknown tab in tab widget"); 1521 | return; 1522 | } 1523 | 1524 | if(!allowClose(toolWindow)) 1525 | return; 1526 | 1527 | if(toolWindowProperties(toolWindow) & ToolWindowManager::HideOnClose) 1528 | hideToolWindow(toolWindow); 1529 | else 1530 | removeToolWindow(toolWindow, true); 1531 | } 1532 | 1533 | void ToolWindowManager::windowTitleChanged(const QString &) 1534 | { 1535 | QWidget *toolWindow = qobject_cast(sender()); 1536 | if(!toolWindow) 1537 | { 1538 | return; 1539 | } 1540 | ToolWindowManagerArea *area = areaOf(toolWindow); 1541 | if(area) 1542 | { 1543 | area->updateToolWindow(toolWindow); 1544 | } 1545 | } 1546 | 1547 | QSplitter *ToolWindowManager::createSplitter() 1548 | { 1549 | QSplitter *splitter = new ToolWindowManagerSplitter(); 1550 | splitter->setChildrenCollapsible(false); 1551 | return splitter; 1552 | } 1553 | 1554 | ToolWindowManager::AreaReference::AreaReference(ToolWindowManager::AreaReferenceType type, 1555 | ToolWindowManagerArea *area, float percentage) 1556 | { 1557 | m_type = type; 1558 | m_percentage = percentage; 1559 | dragResult = false; 1560 | setWidget(area); 1561 | } 1562 | 1563 | void ToolWindowManager::AreaReference::setWidget(QWidget *widget) 1564 | { 1565 | if(m_type == LastUsedArea || m_type == NewFloatingArea || m_type == NoArea || m_type == EmptySpace) 1566 | { 1567 | if(widget != 0) 1568 | { 1569 | qWarning("area parameter ignored for this type"); 1570 | } 1571 | m_widget = 0; 1572 | } 1573 | else if(m_type == AddTo) 1574 | { 1575 | m_widget = qobject_cast(widget); 1576 | if(!m_widget) 1577 | { 1578 | qWarning("only ToolWindowManagerArea can be used with this type"); 1579 | } 1580 | } 1581 | else 1582 | { 1583 | if(!qobject_cast(widget) && !qobject_cast(widget)) 1584 | { 1585 | qWarning("only ToolWindowManagerArea or splitter can be used with this type"); 1586 | m_widget = 0; 1587 | } 1588 | else 1589 | { 1590 | m_widget = widget; 1591 | } 1592 | } 1593 | } 1594 | 1595 | ToolWindowManagerArea *ToolWindowManager::AreaReference::area() const 1596 | { 1597 | return qobject_cast(m_widget); 1598 | } 1599 | 1600 | ToolWindowManager::AreaReference::AreaReference(ToolWindowManager::AreaReferenceType type, 1601 | QWidget *widget) 1602 | { 1603 | m_type = type; 1604 | dragResult = false; 1605 | setWidget(widget); 1606 | } 1607 | -------------------------------------------------------------------------------- /src/ToolWindowManager.h: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014 Pavel Strakhov 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | * 24 | */ 25 | #ifndef TOOLWINDOWMANAGER_H 26 | #define TOOLWINDOWMANAGER_H 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #include 35 | 36 | class ToolWindowManagerArea; 37 | class ToolWindowManagerWrapper; 38 | 39 | class QLabel; 40 | class QSplitter; 41 | 42 | /*! 43 | * \brief The ToolWindowManager class provides docking tool behavior. 44 | * 45 | * The behavior is similar to tool windows mechanism in Visual Studio or Eclipse. 46 | * User can arrange tool windows 47 | * in tabs, dock it to any border, split with vertical and horizontal splitters, 48 | * tabify them together and detach to floating windows. 49 | * 50 | * See https://github.com/Riateche/toolwindowmanager for detailed description. 51 | */ 52 | class ToolWindowManager : public QWidget 53 | { 54 | Q_OBJECT 55 | /*! 56 | * \brief Whether or not to allow floating windows to be created. 57 | * 58 | * Default value is to allow it. 59 | * 60 | * Access functions: allowFloatingWindow, setAllowFloatingWindow. 61 | * 62 | */ 63 | Q_PROPERTY(int allowFloatingWindow READ allowFloatingWindow WRITE setAllowFloatingWindow) 64 | 65 | /*! 66 | * \brief How much of a margin should be placed between drop hotspots. 67 | * 68 | * Default value is 4. 69 | * 70 | * Access functions: dropHotspotMargin, setDropHotspotMargin. 71 | * 72 | */ 73 | Q_PROPERTY(int dropHotspotMargin READ dropHotspotMargin WRITE setDropHotspotMargin) 74 | 75 | /*! 76 | * \brief How wide and heigh each drop hotspot icon should be drawn at, in pixels. 77 | * 78 | * Default value is 32. 79 | * 80 | * Access functions: dropHotspotDimension, setDropHotspotDimension. 81 | * 82 | */ 83 | Q_PROPERTY(int dropHotspotDimension READ dropHotspotDimension WRITE setDropHotspotDimension) 84 | 85 | public: 86 | /*! 87 | * \brief Creates a manager with given \a parent. 88 | */ 89 | explicit ToolWindowManager(QWidget *parent = 0); 90 | /*! 91 | * \brief Destroys the widget. Additionally all tool windows and all floating windows 92 | * created by this widget are destroyed. 93 | */ 94 | virtual ~ToolWindowManager(); 95 | 96 | //! Toolwindow properties 97 | enum ToolWindowProperty 98 | { 99 | //! Disables all drag/docking ability by the user 100 | DisallowUserDocking = 0x1, 101 | //! Hides the close button on the tab for this tool window 102 | HideCloseButton = 0x2, 103 | //! Disable the user being able to drag this tab in the tab bar, to rearrange 104 | DisableDraggableTab = 0x4, 105 | //! When the tool window is closed, hide it instead of removing it 106 | HideOnClose = 0x8, 107 | //! Don't allow this tool window to be floated 108 | DisallowFloatWindow = 0x10, 109 | //! When displaying this tool window in tabs, always display the tabs even if there's only one 110 | AlwaysDisplayFullTabs = 0x20, 111 | }; 112 | 113 | //! Type of AreaReference. 114 | enum AreaReferenceType 115 | { 116 | //! The area tool windows has been added to most recently. 117 | LastUsedArea, 118 | //! New area in a detached window. 119 | NewFloatingArea, 120 | //! Area inside the manager widget (only available when there is no tool windows in it). 121 | EmptySpace, 122 | //! Tool window is hidden. 123 | NoArea, 124 | //! Existing area specified in AreaReference argument. 125 | AddTo, 126 | //! New area to the left of the area specified in AreaReference argument. 127 | LeftOf, 128 | //! New area to the right of the area specified in AreaReference argument. 129 | RightOf, 130 | //! New area to the top of the area specified in AreaReference argument. 131 | TopOf, 132 | //! New area to the bottom of the area specified in AreaReference argument. 133 | BottomOf, 134 | //! New area to the left of the window containing the specified in AreaReference argument. 135 | LeftWindowSide, 136 | //! New area to the right of the window containing the specified in AreaReference argument. 137 | RightWindowSide, 138 | //! New area to the top of the window containing the specified in AreaReference argument. 139 | TopWindowSide, 140 | //! New area to the bottom of the window containing the specified in AreaReference argument. 141 | BottomWindowSide, 142 | //! Invalid value, just indicates the number of types available 143 | NumReferenceTypes 144 | }; 145 | 146 | /*! 147 | * \brief The AreaReference class represents a place where tool windows should be moved. 148 | */ 149 | class AreaReference 150 | { 151 | public: 152 | /*! 153 | * Creates an area reference of the given \a type. If \a type requires specifying 154 | * area, it should be given in \a area argument. Otherwise \a area should have default value 155 | * (0). 156 | */ 157 | AreaReference(AreaReferenceType type = NoArea, ToolWindowManagerArea *area = 0, 158 | float percentage = 0.5f); 159 | //! Returns type of the reference. 160 | AreaReferenceType type() const { return m_type; } 161 | //! Returns area of the reference, or 0 if it was not specified. 162 | ToolWindowManagerArea *area() const; 163 | 164 | private: 165 | AreaReferenceType m_type; 166 | QWidget *m_widget; 167 | float m_percentage; 168 | bool dragResult; 169 | QWidget *widget() const { return m_widget; } 170 | float percentage() const { return m_percentage; } 171 | AreaReference(AreaReferenceType type, QWidget *widget); 172 | void setWidget(QWidget *widget); 173 | 174 | friend class ToolWindowManager; 175 | }; 176 | 177 | /*! 178 | * Adds \a toolWindow to the manager and moves it to the position specified by 179 | * \a area. This function is a shortcut for ToolWindowManager::addToolWindows. 180 | */ 181 | void addToolWindow(QWidget *toolWindow, const AreaReference &area, 182 | ToolWindowProperty properties = ToolWindowProperty(0)); 183 | 184 | /*! 185 | * Sets the set of \a properties on \a toolWindow that is already added to the manager. 186 | */ 187 | void setToolWindowProperties(QWidget *toolWindow, ToolWindowProperty properties); 188 | 189 | /*! 190 | * Returns the set of \a properties on \a toolWindow. 191 | */ 192 | ToolWindowProperty toolWindowProperties(QWidget *toolWindow); 193 | 194 | /*! 195 | * \brief Adds \a toolWindows to the manager and moves it to the position specified by 196 | * \a area. 197 | * The manager takes ownership of the tool windows and will delete them upon destruction. 198 | * 199 | * toolWindow->windowIcon() and toolWindow->windowTitle() will be used as the icon and title 200 | * of the tab that represents the tool window. 201 | * 202 | * If you intend to use ToolWindowManager::saveState 203 | * and ToolWindowManager::restoreState functions, you must set objectName() of each added 204 | * tool window to a non-empty unique string. 205 | */ 206 | void addToolWindows(QList toolWindows, const AreaReference &area, 207 | ToolWindowProperty properties = ToolWindowProperty(0)); 208 | 209 | /*! 210 | * Returns area that contains \a toolWindow, or 0 if \a toolWindow is hidden. 211 | */ 212 | ToolWindowManagerArea *areaOf(QWidget *toolWindow); 213 | 214 | /*! 215 | * \brief Moves \a toolWindow to the position specified by \a area. 216 | * 217 | * \a toolWindow must be added to the manager prior to calling this function. 218 | */ 219 | void moveToolWindow(QWidget *toolWindow, AreaReference area); 220 | 221 | /*! 222 | * \brief Moves \a toolWindows to the position specified by \a area. 223 | * 224 | * \a toolWindows must be added to the manager prior to calling this function. 225 | */ 226 | void moveToolWindows(QList toolWindows, AreaReference area); 227 | 228 | /*! 229 | * \brief Removes \a toolWindow from the manager. \a toolWindow becomes a hidden 230 | * top level widget. The ownership of \a toolWindow is returned to the caller. 231 | */ 232 | void removeToolWindow(QWidget *toolWindow) { removeToolWindow(toolWindow, false); } 233 | /*! 234 | * Returns if \a toolWindow is floating instead of being docked. 235 | */ 236 | bool isFloating(QWidget *toolWindow); 237 | 238 | /*! 239 | * \brief Returns all tool window added to the manager. 240 | */ 241 | const QList &toolWindows() { return m_toolWindows; } 242 | /*! 243 | * Hides \a toolWindow. 244 | * 245 | * \a toolWindow must be added to the manager prior to calling this function. 246 | */ 247 | void hideToolWindow(QWidget *toolWindow) { moveToolWindow(toolWindow, NoArea); } 248 | static ToolWindowManager *managerOf(QWidget *toolWindow); 249 | static void closeToolWindow(QWidget *toolWindow); 250 | static void raiseToolWindow(QWidget *toolWindow); 251 | 252 | /*! 253 | * Force close a window, to be used when a toolWindow has become orphaned 254 | * (no valid parent) 255 | */ 256 | void forceCloseToolWindow(QWidget *toolWindow); 257 | 258 | /*! 259 | * \brief saveState 260 | */ 261 | QVariantMap saveState(); 262 | 263 | /*! 264 | * \brief restoreState 265 | */ 266 | void restoreState(const QVariantMap &data); 267 | 268 | typedef std::function CreateCallback; 269 | 270 | void setToolWindowCreateCallback(const CreateCallback &cb) { m_createCallback = cb; } 271 | QWidget *createToolWindow(const QString &objectName); 272 | 273 | void setHotspotPixmap(AreaReferenceType ref, const QPixmap &pix) { m_pixmaps[ref] = pix; } 274 | void setDropHotspotMargin(int pixels); 275 | bool dropHotspotMargin() { return m_dropHotspotMargin; } 276 | void setDropHotspotDimension(int pixels); 277 | bool dropHotspotDimension() { return m_dropHotspotDimension; } 278 | /*! \cond PRIVATE */ 279 | void setAllowFloatingWindow(bool pixels); 280 | bool allowFloatingWindow() { return m_allowFloatingWindow; } 281 | /*! \endcond */ 282 | 283 | signals: 284 | /*! 285 | * \brief This signal is emitted when \a toolWindow may be hidden or shown. 286 | * \a visible indicates new visibility state of the tool window. 287 | */ 288 | void toolWindowVisibilityChanged(QWidget *toolWindow, bool visible); 289 | 290 | private: 291 | QList m_toolWindows; // all added tool windows 292 | QHash m_toolWindowProperties; // all tool window properties 293 | QList m_areas; // all areas for this manager 294 | QList m_wrappers; // all wrappers for this manager 295 | // list of tool windows that are currently dragged, or empty list if there is no current drag 296 | QList> m_draggedToolWindows; 297 | // the wrapper if a whole float window is being dragged 298 | QPointer m_draggedWrapper; 299 | QPointer m_hoverArea; // the area currently being hovered over in a drag 300 | // a semi-transparent preview of where the dragged toolwindow(s) will be docked 301 | QWidget *m_previewOverlay; 302 | QWidget *m_previewTabOverlay; 303 | QLabel *m_dropHotspots[NumReferenceTypes]; 304 | QPixmap m_pixmaps[NumReferenceTypes]; 305 | 306 | bool m_allowFloatingWindow; // Allow floating windows from this docking area 307 | int m_dropHotspotMargin; // The pixels between drop hotspot icons 308 | int m_dropHotspotDimension; // The pixel dimension of the hotspot icons 309 | 310 | CreateCallback m_createCallback; 311 | 312 | ToolWindowManagerWrapper *wrapperOf(QWidget *toolWindow); 313 | 314 | void drawHotspotPixmaps(); 315 | 316 | bool allowClose(QWidget *toolWindow); 317 | 318 | void removeToolWindow(QWidget *toolWindow, bool allowCloseAlreadyChecked); 319 | 320 | // last widget used for adding tool windows, or 0 if there isn't one 321 | // (warning: may contain pointer to deleted object) 322 | ToolWindowManagerArea *m_lastUsedArea; 323 | // remove tool window from its area (if any) and set parent to 0 324 | void releaseToolWindow(QWidget *toolWindow); 325 | void simplifyLayout(); // remove constructions that became useless 326 | void startDrag(const QList &toolWindows, ToolWindowManagerWrapper *wrapper); 327 | 328 | QVariantMap saveSplitterState(QSplitter *splitter); 329 | QSplitter *restoreSplitterState(const QVariantMap &data); 330 | 331 | AreaReferenceType currentHotspot(); 332 | 333 | void updateDragPosition(); 334 | void abortDrag(); 335 | void finishDrag(); 336 | bool dragInProgress() { return !m_draggedToolWindows.isEmpty(); } 337 | friend class ToolWindowManagerArea; 338 | friend class ToolWindowManagerWrapper; 339 | 340 | protected: 341 | //! Event filter for grabbing and processing drag aborts. 342 | virtual bool eventFilter(QObject *object, QEvent *event); 343 | 344 | /*! 345 | * \brief Creates new splitter and sets its default properties. You may reimplement 346 | * this function to change properties of all splitters used by this class. 347 | */ 348 | virtual QSplitter *createSplitter(); 349 | /*! 350 | * \brief Creates new area and sets its default properties. You may reimplement 351 | * this function to change properties of all tab widgets used by this class. 352 | */ 353 | virtual ToolWindowManagerArea *createArea(); 354 | 355 | private slots: 356 | void tabCloseRequested(int index); 357 | void windowTitleChanged(const QString &title); 358 | }; 359 | 360 | inline ToolWindowManager::ToolWindowProperty operator|(ToolWindowManager::ToolWindowProperty a, 361 | ToolWindowManager::ToolWindowProperty b) 362 | { 363 | return ToolWindowManager::ToolWindowProperty(int(a) | int(b)); 364 | } 365 | 366 | #endif // TOOLWINDOWMANAGER_H 367 | -------------------------------------------------------------------------------- /src/ToolWindowManagerArea.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014 Pavel Strakhov 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | * 24 | */ 25 | #include "ToolWindowManagerArea.h" 26 | #include 27 | #include 28 | #include 29 | #include "ToolWindowManager.h" 30 | #include "ToolWindowManagerTabBar.h" 31 | #include "ToolWindowManagerWrapper.h" 32 | 33 | static void showCloseButton(QTabBar *bar, int index, bool show) 34 | { 35 | QWidget *button = bar->tabButton(index, QTabBar::RightSide); 36 | if(button == NULL) 37 | button = bar->tabButton(index, QTabBar::LeftSide); 38 | 39 | if(button) 40 | { 41 | button->resize(show ? QSize(16, 16) : QSize(1, 1)); 42 | button->setVisible(show); 43 | } 44 | } 45 | 46 | ToolWindowManagerArea::ToolWindowManagerArea(ToolWindowManager *manager, QWidget *parent) 47 | : QTabWidget(parent), m_manager(manager) 48 | { 49 | m_tabBar = new ToolWindowManagerTabBar(this); 50 | setTabBar(m_tabBar); 51 | 52 | m_tabBar->setTabsClosable(true); 53 | 54 | m_dragCanStart = false; 55 | m_tabDragCanStart = false; 56 | m_inTabMoved = false; 57 | m_userCanDrop = true; 58 | setMovable(true); 59 | setDocumentMode(true); 60 | tabBar()->installEventFilter(this); 61 | m_manager->m_areas << this; 62 | 63 | QObject::connect(tabBar(), &QTabBar::tabMoved, this, &ToolWindowManagerArea::tabMoved); 64 | QObject::connect(tabBar(), &QTabBar::tabCloseRequested, this, &ToolWindowManagerArea::tabClosing); 65 | QObject::connect(tabBar(), &QTabBar::tabCloseRequested, this, &QTabWidget::tabCloseRequested); 66 | QObject::connect(this, &QTabWidget::currentChanged, this, &ToolWindowManagerArea::tabSelected); 67 | } 68 | 69 | ToolWindowManagerArea::~ToolWindowManagerArea() 70 | { 71 | m_manager->m_areas.removeOne(this); 72 | } 73 | 74 | void ToolWindowManagerArea::addToolWindow(QWidget *toolWindow, int insertIndex) 75 | { 76 | addToolWindows(QList() << toolWindow, insertIndex); 77 | } 78 | 79 | void ToolWindowManagerArea::addToolWindows(const QList &toolWindows, int insertIndex) 80 | { 81 | int index = 0; 82 | foreach(QWidget *toolWindow, toolWindows) 83 | { 84 | index = insertTab(insertIndex, toolWindow, toolWindow->windowIcon(), toolWindow->windowTitle()); 85 | insertIndex = index + 1; 86 | } 87 | setCurrentIndex(index); 88 | for(int i = 0; i < count(); i++) 89 | { 90 | updateToolWindow(widget(i)); 91 | } 92 | m_manager->m_lastUsedArea = this; 93 | } 94 | 95 | QList ToolWindowManagerArea::toolWindows() 96 | { 97 | QList result; 98 | for(int i = 0; i < count(); i++) 99 | { 100 | result << widget(i); 101 | } 102 | return result; 103 | } 104 | 105 | void ToolWindowManagerArea::updateToolWindow(QWidget *toolWindow) 106 | { 107 | int index = indexOf(toolWindow); 108 | if(index >= 0) 109 | { 110 | ToolWindowManagerTabBar *tb = static_cast(tabBar()); 111 | if(m_manager->toolWindowProperties(toolWindow) & ToolWindowManager::HideCloseButton) 112 | showCloseButton(tabBar(), index, false); 113 | else 114 | showCloseButton(tabBar(), index, true); 115 | tabBar()->setTabText(index, toolWindow->windowTitle()); 116 | } 117 | } 118 | 119 | void ToolWindowManagerArea::mouseMoveEvent(QMouseEvent *) 120 | { 121 | check_mouse_move(); 122 | } 123 | 124 | bool ToolWindowManagerArea::eventFilter(QObject *object, QEvent *event) 125 | { 126 | if(object == tabBar()) 127 | { 128 | if(event->type() == QEvent::MouseButtonPress && qApp->mouseButtons() == Qt::LeftButton) 129 | { 130 | QPoint pos = static_cast(event)->pos(); 131 | 132 | int tabIndex = tabBar()->tabAt(pos); 133 | 134 | // can start tab drag only if mouse is at some tab, not at empty tabbar space 135 | if(tabIndex >= 0) 136 | { 137 | m_tabDragCanStart = true; 138 | 139 | if(m_manager->toolWindowProperties(widget(tabIndex)) & ToolWindowManager::DisableDraggableTab) 140 | { 141 | setMovable(false); 142 | } 143 | else 144 | { 145 | setMovable(true); 146 | } 147 | } 148 | else if(m_tabBar == NULL || !m_tabBar->inButton(pos)) 149 | { 150 | m_dragCanStart = true; 151 | m_dragCanStartPos = QCursor::pos(); 152 | } 153 | } 154 | else if(event->type() == QEvent::MouseButtonPress && qApp->mouseButtons() == Qt::MiddleButton) 155 | { 156 | int tabIndex = tabBar()->tabAt(static_cast(event)->pos()); 157 | 158 | if(tabIndex >= 0) 159 | { 160 | QWidget *w = widget(tabIndex); 161 | 162 | if(!(m_manager->toolWindowProperties(w) & ToolWindowManager::HideCloseButton)) 163 | { 164 | emit tabCloseRequested(tabIndex); 165 | } 166 | } 167 | } 168 | else if(event->type() == QEvent::MouseButtonRelease) 169 | { 170 | m_tabDragCanStart = false; 171 | m_dragCanStart = false; 172 | m_manager->updateDragPosition(); 173 | } 174 | else if(event->type() == QEvent::MouseMove) 175 | { 176 | m_manager->updateDragPosition(); 177 | if(m_tabDragCanStart) 178 | { 179 | if(tabBar()->rect().contains(static_cast(event)->pos())) 180 | { 181 | return false; 182 | } 183 | if(qApp->mouseButtons() != Qt::LeftButton) 184 | { 185 | return false; 186 | } 187 | QWidget *toolWindow = currentWidget(); 188 | if(!toolWindow || !m_manager->m_toolWindows.contains(toolWindow)) 189 | { 190 | return false; 191 | } 192 | m_tabDragCanStart = false; 193 | // stop internal tab drag in QTabBar 194 | QMouseEvent *releaseEvent = 195 | new QMouseEvent(QEvent::MouseButtonRelease, static_cast(event)->pos(), 196 | Qt::LeftButton, Qt::LeftButton, 0); 197 | qApp->sendEvent(tabBar(), releaseEvent); 198 | m_manager->startDrag(QList() << toolWindow, NULL); 199 | } 200 | else if(m_dragCanStart) 201 | { 202 | check_mouse_move(); 203 | } 204 | } 205 | } 206 | return QTabWidget::eventFilter(object, event); 207 | } 208 | 209 | void ToolWindowManagerArea::tabInserted(int index) 210 | { 211 | // update the select order. Increment any existing index after the insertion point to keep the 212 | // indices in the list up to date. 213 | for(int &idx : m_tabSelectOrder) 214 | { 215 | if(idx >= index) 216 | idx++; 217 | } 218 | 219 | // if the tab inserted is the current index (most likely) then add it at the end, otherwise 220 | // add it next-to-end (to keep the most recent tab the same). 221 | if(currentIndex() == index || m_tabSelectOrder.isEmpty()) 222 | m_tabSelectOrder.append(index); 223 | else 224 | m_tabSelectOrder.insert(m_tabSelectOrder.count() - 1, index); 225 | 226 | QTabWidget::tabInserted(index); 227 | } 228 | 229 | void ToolWindowManagerArea::tabRemoved(int index) 230 | { 231 | // update the select order. Remove the index that just got deleted, and decrement any index 232 | // greater than it to remap to their new indices 233 | m_tabSelectOrder.removeOne(index); 234 | 235 | for(int &idx : m_tabSelectOrder) 236 | { 237 | if(idx > index) 238 | idx--; 239 | } 240 | 241 | QTabWidget::tabRemoved(index); 242 | } 243 | 244 | void ToolWindowManagerArea::tabSelected(int index) 245 | { 246 | // move this tab to the end of the select order, as long as we have it - if it's a new index then 247 | // ignore and leave it to be handled in tabInserted() 248 | if(m_tabSelectOrder.contains(index)) 249 | { 250 | m_tabSelectOrder.removeOne(index); 251 | m_tabSelectOrder.append(index); 252 | } 253 | 254 | ToolWindowManagerWrapper *wrapper = m_manager->wrapperOf(this); 255 | if(wrapper) 256 | wrapper->updateTitle(); 257 | } 258 | 259 | void ToolWindowManagerArea::tabClosing(int index) 260 | { 261 | // before closing this index, switch the current index to the next tab in succession. 262 | 263 | // should never get here but let's check this 264 | if(m_tabSelectOrder.isEmpty()) 265 | return; 266 | 267 | // when closing the last tab there's nothing to do 268 | if(m_tabSelectOrder.count() == 1) 269 | return; 270 | 271 | // if the last in the select order is being closed, switch to the next most selected tab 272 | if(m_tabSelectOrder.last() == index) 273 | setCurrentIndex(m_tabSelectOrder.at(m_tabSelectOrder.count() - 2)); 274 | } 275 | 276 | QVariantMap ToolWindowManagerArea::saveState() 277 | { 278 | QVariantMap result; 279 | result[QStringLiteral("type")] = QStringLiteral("area"); 280 | result[QStringLiteral("currentIndex")] = currentIndex(); 281 | QVariantList objects; 282 | objects.reserve(count()); 283 | for(int i = 0; i < count(); i++) 284 | { 285 | QWidget *w = widget(i); 286 | QString name = w->objectName(); 287 | if(name.isEmpty()) 288 | { 289 | qWarning("cannot save state of tool window without object name"); 290 | } 291 | else 292 | { 293 | QVariantMap objectData; 294 | objectData[QStringLiteral("name")] = name; 295 | objectData[QStringLiteral("data")] = w->property("persistData"); 296 | objects.push_back(objectData); 297 | } 298 | } 299 | result[QStringLiteral("objects")] = objects; 300 | return result; 301 | } 302 | 303 | void ToolWindowManagerArea::restoreState(const QVariantMap &savedData) 304 | { 305 | for(QVariant object : savedData[QStringLiteral("objects")].toList()) 306 | { 307 | QVariantMap objectData = object.toMap(); 308 | if(objectData.isEmpty()) 309 | { 310 | continue; 311 | } 312 | QString objectName = objectData[QStringLiteral("name")].toString(); 313 | if(objectName.isEmpty()) 314 | { 315 | continue; 316 | } 317 | QWidget *t = NULL; 318 | for(QWidget *toolWindow : m_manager->m_toolWindows) 319 | { 320 | if(toolWindow->objectName() == objectName) 321 | { 322 | t = toolWindow; 323 | break; 324 | } 325 | } 326 | if(t == NULL) 327 | t = m_manager->createToolWindow(objectName); 328 | if(t) 329 | { 330 | t->setProperty("persistData", objectData[QStringLiteral("data")]); 331 | addToolWindow(t); 332 | } 333 | else 334 | { 335 | qWarning("tool window with name '%s' not found or created", 336 | objectName.toLocal8Bit().constData()); 337 | } 338 | } 339 | setCurrentIndex(savedData[QStringLiteral("currentIndex")].toInt()); 340 | } 341 | 342 | void ToolWindowManagerArea::check_mouse_move() 343 | { 344 | if(qApp->mouseButtons() != Qt::LeftButton && m_dragCanStart) 345 | { 346 | m_dragCanStart = false; 347 | } 348 | m_manager->updateDragPosition(); 349 | if(m_dragCanStart && (QCursor::pos() - m_dragCanStartPos).manhattanLength() > 10) 350 | { 351 | m_dragCanStart = false; 352 | QList toolWindows; 353 | for(int i = 0; i < count(); i++) 354 | { 355 | QWidget *toolWindow = widget(i); 356 | if(!m_manager->m_toolWindows.contains(toolWindow)) 357 | { 358 | qWarning("tab widget contains unmanaged widget"); 359 | } 360 | else 361 | { 362 | toolWindows << toolWindow; 363 | } 364 | } 365 | m_manager->startDrag(toolWindows, NULL); 366 | } 367 | } 368 | 369 | bool ToolWindowManagerArea::useMinimalTabBar() 370 | { 371 | QWidget *w = widget(0); 372 | if(w == NULL) 373 | return false; 374 | 375 | return (m_manager->toolWindowProperties(w) & ToolWindowManager::AlwaysDisplayFullTabs) == 0; 376 | } 377 | 378 | void ToolWindowManagerArea::tabMoved(int from, int to) 379 | { 380 | if(m_inTabMoved) 381 | return; 382 | 383 | // update the select order. 384 | // This amounts to just a swap - any indices other than the pair in question are unaffected since 385 | // one tab is removed (above/below) and added (below/above) so the indices themselves remain the 386 | // same. 387 | for(int &idx : m_tabSelectOrder) 388 | { 389 | if(idx == from) 390 | idx = to; 391 | else if(idx == to) 392 | idx = from; 393 | } 394 | 395 | QWidget *a = widget(from); 396 | QWidget *b = widget(to); 397 | 398 | if(!a || !b) 399 | return; 400 | 401 | if(m_manager->toolWindowProperties(a) & ToolWindowManager::DisableDraggableTab || 402 | m_manager->toolWindowProperties(b) & ToolWindowManager::DisableDraggableTab) 403 | { 404 | m_inTabMoved = true; 405 | tabBar()->moveTab(to, from); 406 | m_inTabMoved = false; 407 | } 408 | } 409 | -------------------------------------------------------------------------------- /src/ToolWindowManagerArea.h: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014 Pavel Strakhov 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | * 24 | */ 25 | #ifndef TOOLWINDOWMANAGERAREA_H 26 | #define TOOLWINDOWMANAGERAREA_H 27 | 28 | #include 29 | #include 30 | 31 | class ToolWindowManager; 32 | class ToolWindowManagerTabBar; 33 | 34 | /*! 35 | * \brief The ToolWindowManagerArea class is a tab widget used to store tool windows. 36 | * It implements dragging of its tab or the whole tab widget. 37 | */ 38 | class ToolWindowManagerArea : public QTabWidget 39 | { 40 | Q_OBJECT 41 | public: 42 | //! Creates new area. 43 | explicit ToolWindowManagerArea(ToolWindowManager *manager, QWidget *parent = 0); 44 | //! Destroys the area. 45 | virtual ~ToolWindowManagerArea(); 46 | 47 | /*! 48 | * Add \a toolWindow to this area. 49 | */ 50 | void addToolWindow(QWidget *toolWindow, int insertIndex = -1); 51 | 52 | /*! 53 | * Add \a toolWindows to this area. 54 | */ 55 | void addToolWindows(const QList &toolWindows, int insertIndex = -1); 56 | 57 | void enableUserDrop() { m_userCanDrop = true; } 58 | void disableUserDrop() { m_userCanDrop = false; } 59 | bool allowUserDrop() { return m_userCanDrop; } 60 | /*! 61 | * Returns a list of all tool windows in this area. 62 | */ 63 | QList toolWindows(); 64 | 65 | ToolWindowManager *manager() { return m_manager; } 66 | /*! 67 | * Updates the \a toolWindow to its current properties and title. 68 | */ 69 | void updateToolWindow(QWidget *toolWindow); 70 | 71 | protected: 72 | //! Reimplemented from QTabWidget::mouseMoveEvent. 73 | virtual void mouseMoveEvent(QMouseEvent *); 74 | //! Reimplemented from QTabWidget::eventFilter. 75 | virtual bool eventFilter(QObject *object, QEvent *event); 76 | 77 | //! Reimplemented from QTabWidget::tabInserted. 78 | virtual void tabInserted(int index); 79 | //! Reimplemented from QTabWidget::tabRemoved. 80 | virtual void tabRemoved(int index); 81 | 82 | private: 83 | ToolWindowManager *m_manager; 84 | ToolWindowManagerTabBar *m_tabBar; 85 | bool m_dragCanStart; // indicates that user has started mouse movement on QTabWidget 86 | // that can be considered as dragging it if the cursor will leave 87 | // its area 88 | QPoint m_dragCanStartPos; // the position the cursor was at 89 | 90 | bool m_tabDragCanStart; // indicates that user has started mouse movement on QTabWidget 91 | // that can be considered as dragging current tab 92 | // if the cursor will leave the tab bar area 93 | 94 | bool m_userCanDrop; // indictes the user is allowed to drop things on this area 95 | 96 | bool m_inTabMoved; // if we're in the tabMoved() function (so if we call tabMove to cancel 97 | // the movement, we shouldn't re-check the tabMoved behaviour) 98 | 99 | QVector 100 | m_tabSelectOrder; // This is the 'history' order of the tabs as they were selected, 101 | // with most recently selected index last. Any time a tab is closed 102 | // we select the last one on the list. 103 | 104 | QVariantMap saveState(); // dump contents to variable 105 | void restoreState(const QVariantMap &data); // restore contents from given variable 106 | 107 | // check if mouse left tab widget area so that dragging should start 108 | void check_mouse_move(); 109 | 110 | bool useMinimalTabBar(); 111 | 112 | friend class ToolWindowManager; 113 | friend class ToolWindowManagerTabBar; 114 | friend class ToolWindowManagerWrapper; 115 | 116 | private slots: 117 | void tabMoved(int from, int to); 118 | void tabSelected(int index); 119 | void tabClosing(int index); 120 | }; 121 | 122 | #endif // TOOLWINDOWMANAGERAREA_H 123 | -------------------------------------------------------------------------------- /src/ToolWindowManagerSplitter.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2017 Baldur Karlsson 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | * 24 | */ 25 | #include "ToolWindowManagerSplitter.h" 26 | #include 27 | #include 28 | 29 | ToolWindowManagerSplitter::ToolWindowManagerSplitter(QWidget *parent) : QSplitter(parent) 30 | { 31 | } 32 | 33 | ToolWindowManagerSplitter::~ToolWindowManagerSplitter() 34 | { 35 | } 36 | 37 | void ToolWindowManagerSplitter::childEvent(QChildEvent *event) 38 | { 39 | QList s = sizes(); 40 | 41 | QWidget *w = qobject_cast(event->child()); 42 | int idx = -1; 43 | if(w) 44 | idx = indexOf(w); 45 | 46 | QSplitter::childEvent(event); 47 | 48 | if(event->type() == QEvent::ChildRemoved && idx >= 0 && idx < s.count()) 49 | { 50 | int removedSize = s[idx]; 51 | 52 | s.removeAt(idx); 53 | 54 | // if we removed an item at one extreme or another, the new end should get all the space 55 | // (unless the list is now empty) 56 | if(idx == 0) 57 | { 58 | if(!s.isEmpty()) 59 | s[0] += removedSize; 60 | } 61 | else if(idx == s.count()) 62 | { 63 | if(!s.isEmpty()) 64 | s[s.count() - 1] += removedSize; 65 | } 66 | else 67 | { 68 | // we removed an item in the middle, share the space between its previous neighbours, now in 69 | // [idx-1] and [idx], and we know they're valid since if there were only two elements before 70 | // the removal one or the other case above would have matched. So there are at least two 71 | // elements now and idx > 0 72 | 73 | s[idx - 1] += removedSize / 2; 74 | s[idx] += removedSize / 2; 75 | } 76 | 77 | setSizes(s); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/ToolWindowManagerSplitter.h: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2017 Baldur Karlsson 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | * 24 | */ 25 | #ifndef TOOLWINDOWMANAGERSPLITTER_H 26 | #define TOOLWINDOWMANAGERSPLITTER_H 27 | 28 | #include 29 | 30 | /*! 31 | * \brief The ToolWindowManagerSplitter class is a splitter that tweaks how sizes are allocated in 32 | * children when a child is removed. 33 | */ 34 | class ToolWindowManagerSplitter : public QSplitter 35 | { 36 | Q_OBJECT 37 | public: 38 | //! Creates new tab bar. 39 | explicit ToolWindowManagerSplitter(QWidget *parent = 0); 40 | //! Destroys the tab bar. 41 | virtual ~ToolWindowManagerSplitter(); 42 | 43 | protected: 44 | //! Reimplemented from QSplitter to share excess space differently. 45 | void childEvent(QChildEvent *) Q_DECL_OVERRIDE; 46 | }; 47 | 48 | #endif // TOOLWINDOWMANAGERSPLITTER_H 49 | -------------------------------------------------------------------------------- /src/ToolWindowManagerTabBar.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2017 Baldur Karlsson 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | * 24 | */ 25 | #include "ToolWindowManager.h" 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include "ToolWindowManagerArea.h" 31 | #include "ToolWindowManagerTabBar.h" 32 | #include "ToolWindowManagerWrapper.h" 33 | 34 | ToolWindowManagerTabBar::ToolWindowManagerTabBar(QWidget *parent) : QTabBar(parent) 35 | { 36 | m_tabsClosable = false; 37 | 38 | setMouseTracking(true); 39 | 40 | m_area = qobject_cast(parent); 41 | 42 | // Workaround for extremely dodgy KDE behaviour - by default the KDE theme will install event 43 | // filters on various widgets such as QTabBar and any descendents, and if a click is detected on 44 | // them that isn't on a tab it will immediately start moving the window, interfering with our own 45 | // click-to-drag behaviour. 46 | setProperty("_kde_no_window_grab", true); 47 | 48 | QStyleOptionToolButton buttonOpt; 49 | 50 | int size = style()->pixelMetric(QStyle::PM_SmallIconSize, 0, this); 51 | 52 | buttonOpt.initFrom(parentWidget()); 53 | buttonOpt.iconSize = QSize(size, size); 54 | buttonOpt.subControls = 0; 55 | buttonOpt.activeSubControls = 0; 56 | buttonOpt.features = QStyleOptionToolButton::None; 57 | buttonOpt.arrowType = Qt::NoArrow; 58 | buttonOpt.state |= QStyle::State_AutoRaise; 59 | 60 | // TODO make our own pin icon, that is pinned/unpinned 61 | m_pin.icon = style()->standardIcon(QStyle::SP_TitleBarNormalButton, &buttonOpt, this); 62 | m_close.icon = style()->standardIcon(QStyle::SP_TitleBarCloseButton, &buttonOpt, this); 63 | 64 | m_pin.hover = m_pin.clicked = false; 65 | m_close.hover = m_close.clicked = false; 66 | } 67 | 68 | ToolWindowManagerTabBar::~ToolWindowManagerTabBar() 69 | { 70 | } 71 | 72 | bool ToolWindowManagerTabBar::useMinimalBar() const 73 | { 74 | if(count() > 1) 75 | return false; 76 | 77 | if(m_area) 78 | { 79 | return m_area->useMinimalTabBar(); 80 | } 81 | return true; 82 | } 83 | 84 | QSize ToolWindowManagerTabBar::sizeHint() const 85 | { 86 | if(useMinimalBar()) 87 | { 88 | if(floatingWindowChild()) 89 | return QSize(0, 0); 90 | 91 | QFontMetrics fm = fontMetrics(); 92 | 93 | int iconSize = style()->pixelMetric(QStyle::PM_SmallIconSize, 0, this); 94 | int mw = style()->pixelMetric(QStyle::PM_DockWidgetTitleMargin, 0, this); 95 | 96 | int h = qMax(fm.height(), iconSize) + 2 * mw; 97 | 98 | return QSize(m_area->width(), h); 99 | } 100 | 101 | return QTabBar::sizeHint(); 102 | } 103 | 104 | QSize ToolWindowManagerTabBar::minimumSizeHint() const 105 | { 106 | if(useMinimalBar()) 107 | { 108 | if(floatingWindowChild()) 109 | return QSize(0, 0); 110 | 111 | QFontMetrics fm = fontMetrics(); 112 | 113 | int iconSize = style()->pixelMetric(QStyle::PM_SmallIconSize, 0, this); 114 | int mw = style()->pixelMetric(QStyle::PM_DockWidgetTitleMargin, 0, this); 115 | 116 | int h = qMax(fm.height(), iconSize) + 2 * mw; 117 | 118 | return QSize(h, h); 119 | } 120 | 121 | return QTabBar::minimumSizeHint(); 122 | } 123 | 124 | bool ToolWindowManagerTabBar::inButton(QPoint pos) 125 | { 126 | return m_pin.rect.contains(pos) || m_close.rect.contains(pos); 127 | } 128 | 129 | void ToolWindowManagerTabBar::paintEvent(QPaintEvent *event) 130 | { 131 | if(useMinimalBar()) 132 | { 133 | if(floatingWindowChild()) 134 | return; 135 | 136 | QStylePainter p(this); 137 | 138 | QStyleOptionDockWidget option; 139 | 140 | option.initFrom(parentWidget()); 141 | option.rect = m_titleRect; 142 | option.title = tabText(0); 143 | option.closable = m_tabsClosable; 144 | option.movable = false; 145 | // we only set floatable true so we can hijack the float button for our own pin/auto-hide button 146 | option.floatable = true; 147 | 148 | Shape s = shape(); 149 | option.verticalTitleBar = 150 | s == RoundedEast || s == TriangularEast || s == RoundedWest || s == TriangularWest; 151 | 152 | p.drawControl(QStyle::CE_DockWidgetTitle, option); 153 | 154 | int size = style()->pixelMetric(QStyle::PM_SmallIconSize, 0, this); 155 | 156 | QStyleOptionToolButton buttonOpt; 157 | 158 | buttonOpt.initFrom(parentWidget()); 159 | buttonOpt.iconSize = QSize(size, size); 160 | buttonOpt.subControls = 0; 161 | buttonOpt.activeSubControls = 0; 162 | buttonOpt.features = QStyleOptionToolButton::None; 163 | buttonOpt.arrowType = Qt::NoArrow; 164 | buttonOpt.state = QStyle::State_Active | QStyle::State_Enabled | QStyle::State_AutoRaise; 165 | 166 | buttonOpt.rect = m_pin.rect; 167 | buttonOpt.icon = m_pin.icon; 168 | 169 | ToolWindowManager::ToolWindowProperty props = 170 | m_area->m_manager->toolWindowProperties(m_area->widget(0)); 171 | 172 | bool tabClosable = (props & ToolWindowManager::HideCloseButton) == 0; 173 | 174 | if(!tabClosable && !m_pin.rect.isEmpty()) 175 | buttonOpt.rect = m_close.rect; 176 | 177 | QStyle::State prevState = buttonOpt.state; 178 | 179 | if(m_pin.clicked) 180 | buttonOpt.state |= QStyle::State_Sunken; 181 | else if(m_pin.hover) 182 | buttonOpt.state |= QStyle::State_Raised | QStyle::State_MouseOver; 183 | 184 | if(style()->styleHint(QStyle::SH_DockWidget_ButtonsHaveFrame, 0, this)) 185 | { 186 | style()->drawPrimitive(QStyle::PE_PanelButtonTool, &buttonOpt, &p, this); 187 | } 188 | 189 | style()->drawComplexControl(QStyle::CC_ToolButton, &buttonOpt, &p, this); 190 | 191 | if(m_tabsClosable && tabClosable) 192 | { 193 | buttonOpt.rect = m_close.rect; 194 | buttonOpt.icon = m_close.icon; 195 | 196 | buttonOpt.state = prevState; 197 | 198 | if(m_close.clicked) 199 | buttonOpt.state |= QStyle::State_Sunken; 200 | else if(m_close.hover) 201 | buttonOpt.state |= QStyle::State_Raised | QStyle::State_MouseOver; 202 | 203 | style()->drawPrimitive(QStyle::PE_IndicatorTabClose, &buttonOpt, &p, this); 204 | } 205 | return; 206 | } 207 | 208 | QTabBar::paintEvent(event); 209 | } 210 | 211 | void ToolWindowManagerTabBar::resizeEvent(QResizeEvent *event) 212 | { 213 | QTabBar::resizeEvent(event); 214 | 215 | if(count() > 1 || floatingWindowChild()) 216 | return; 217 | 218 | m_titleRect = QRect(0, 0, size().width(), sizeHint().height()); 219 | 220 | QStyleOptionDockWidget option; 221 | 222 | option.initFrom(parentWidget()); 223 | option.rect = m_titleRect; 224 | option.closable = m_tabsClosable; 225 | option.movable = false; 226 | // we only set floatable true so we can hijack the float button for our own pin/auto-hide button 227 | option.floatable = true; 228 | 229 | m_pin.rect = style()->subElementRect(QStyle::SE_DockWidgetFloatButton, &option, this); 230 | m_close.rect = style()->subElementRect(QStyle::SE_DockWidgetCloseButton, &option, this); 231 | 232 | // TODO - temporarily until this is implemented, hide the pin button. 233 | m_pin.rect = QRect(); 234 | } 235 | 236 | void ToolWindowManagerTabBar::mousePressEvent(QMouseEvent *event) 237 | { 238 | QTabBar::mousePressEvent(event); 239 | 240 | if(count() > 1 || floatingWindowChild()) 241 | return; 242 | 243 | ButtonData prevPin = m_pin; 244 | ButtonData prevClose = m_close; 245 | 246 | ToolWindowManager::ToolWindowProperty props = 247 | m_area->m_manager->toolWindowProperties(m_area->widget(0)); 248 | 249 | bool tabClosable = (props & ToolWindowManager::HideCloseButton) == 0; 250 | 251 | QRect pinRect = m_pin.rect; 252 | QRect closeRect = m_close.rect; 253 | 254 | if(!tabClosable) 255 | { 256 | if(!pinRect.isEmpty()) 257 | pinRect = closeRect; 258 | closeRect = QRect(); 259 | } 260 | 261 | if(pinRect.contains(mapFromGlobal(QCursor::pos())) && event->buttons() & Qt::LeftButton) 262 | { 263 | m_pin.clicked = true; 264 | } 265 | else 266 | { 267 | m_pin.clicked = false; 268 | } 269 | 270 | if(closeRect.contains(mapFromGlobal(QCursor::pos())) && event->buttons() & Qt::LeftButton) 271 | { 272 | m_close.clicked = true; 273 | } 274 | else 275 | { 276 | m_close.clicked = false; 277 | } 278 | 279 | if(prevPin != m_pin || prevClose != m_close) 280 | update(); 281 | 282 | event->accept(); 283 | } 284 | 285 | void ToolWindowManagerTabBar::mouseMoveEvent(QMouseEvent *event) 286 | { 287 | QTabBar::mouseMoveEvent(event); 288 | 289 | if(count() > 1 || floatingWindowChild()) 290 | return; 291 | 292 | ButtonData prevPin = m_pin; 293 | ButtonData prevClose = m_close; 294 | 295 | ToolWindowManager::ToolWindowProperty props = 296 | m_area->m_manager->toolWindowProperties(m_area->widget(0)); 297 | 298 | bool tabClosable = (props & ToolWindowManager::HideCloseButton) == 0; 299 | 300 | QRect pinRect = m_pin.rect; 301 | QRect closeRect = m_close.rect; 302 | 303 | if(!tabClosable) 304 | { 305 | if(!pinRect.isEmpty()) 306 | pinRect = closeRect; 307 | closeRect = QRect(); 308 | } 309 | 310 | if(pinRect.contains(mapFromGlobal(QCursor::pos()))) 311 | { 312 | m_pin.hover = true; 313 | if(event->buttons() & Qt::LeftButton) 314 | m_pin.clicked = true; 315 | } 316 | else 317 | { 318 | m_pin.hover = false; 319 | m_pin.clicked = false; 320 | } 321 | 322 | if(closeRect.contains(mapFromGlobal(QCursor::pos()))) 323 | { 324 | m_close.hover = true; 325 | if(event->buttons() & Qt::LeftButton) 326 | m_close.clicked = true; 327 | } 328 | else 329 | { 330 | m_close.hover = false; 331 | m_close.clicked = false; 332 | } 333 | 334 | if(prevPin != m_pin || prevClose != m_close) 335 | update(); 336 | } 337 | 338 | void ToolWindowManagerTabBar::leaveEvent(QEvent *) 339 | { 340 | m_pin.hover = false; 341 | m_pin.clicked = false; 342 | 343 | m_close.hover = false; 344 | m_close.clicked = false; 345 | 346 | update(); 347 | } 348 | 349 | void ToolWindowManagerTabBar::mouseReleaseEvent(QMouseEvent *event) 350 | { 351 | QTabBar::mouseReleaseEvent(event); 352 | 353 | if(count() > 1 || floatingWindowChild()) 354 | return; 355 | 356 | ToolWindowManager::ToolWindowProperty props = 357 | m_area->m_manager->toolWindowProperties(m_area->widget(0)); 358 | 359 | bool tabClosable = (props & ToolWindowManager::HideCloseButton) == 0; 360 | 361 | QRect pinRect = m_pin.rect; 362 | QRect closeRect = m_close.rect; 363 | 364 | if(!tabClosable) 365 | { 366 | if(!pinRect.isEmpty()) 367 | pinRect = closeRect; 368 | closeRect = QRect(); 369 | } 370 | 371 | if(pinRect.contains(mapFromGlobal(QCursor::pos()))) 372 | { 373 | // process a pin of these tabs 374 | 375 | m_pin.clicked = false; 376 | 377 | update(); 378 | 379 | event->accept(); 380 | } 381 | 382 | if(closeRect.contains(mapFromGlobal(QCursor::pos()))) 383 | { 384 | if(m_area) 385 | m_area->tabCloseRequested(0); 386 | 387 | m_close.clicked = false; 388 | 389 | update(); 390 | 391 | event->accept(); 392 | } 393 | } 394 | 395 | void ToolWindowManagerTabBar::tabInserted(int) 396 | { 397 | updateClosable(); 398 | } 399 | 400 | void ToolWindowManagerTabBar::tabRemoved(int) 401 | { 402 | updateClosable(); 403 | } 404 | 405 | void ToolWindowManagerTabBar::updateClosable() 406 | { 407 | QTabBar::setTabsClosable(m_tabsClosable && !useMinimalBar()); 408 | } 409 | 410 | bool ToolWindowManagerTabBar::floatingWindowChild() const 411 | { 412 | ToolWindowManagerArea *area = qobject_cast(parentWidget()); 413 | 414 | if(area) 415 | { 416 | ToolWindowManagerWrapper *wrapper = 417 | qobject_cast(area->parentWidget()); 418 | 419 | if(wrapper && wrapper->floating()) 420 | return true; 421 | } 422 | 423 | return false; 424 | } 425 | -------------------------------------------------------------------------------- /src/ToolWindowManagerTabBar.h: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2017 Baldur Karlsson 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | * 24 | */ 25 | #ifndef TOOLWINDOWMANAGERTABBAR_H 26 | #define TOOLWINDOWMANAGERTABBAR_H 27 | 28 | #include 29 | #include 30 | 31 | class ToolWindowManager; 32 | class ToolWindowManagerArea; 33 | 34 | /*! 35 | * \brief The ToolWindowManagerTabBar class is a tab bar used to customise the painting 36 | * in the case that there's only only one child widget. 37 | */ 38 | class ToolWindowManagerTabBar : public QTabBar 39 | { 40 | Q_OBJECT 41 | public: 42 | //! Creates new tab bar. 43 | explicit ToolWindowManagerTabBar(QWidget *parent = 0); 44 | //! Destroys the tab bar. 45 | virtual ~ToolWindowManagerTabBar(); 46 | 47 | bool tabsClosable() const { return m_tabsClosable; } 48 | void setTabsClosable(bool closable) 49 | { 50 | m_tabsClosable = closable; 51 | updateClosable(); 52 | } 53 | 54 | //! Reimplemented from QTabWidget::QTabBar to custom size for the single tab case. 55 | QSize sizeHint() const Q_DECL_OVERRIDE; 56 | 57 | bool useMinimalBar() const; 58 | 59 | QSize minimumSizeHint() const Q_DECL_OVERRIDE; 60 | 61 | //! is this point in a custom titlebar button 62 | bool inButton(QPoint pos); 63 | 64 | protected: 65 | //! Reimplemented from QTabWidget::QTabBar to custom paint for the single tab case. 66 | void paintEvent(QPaintEvent *) Q_DECL_OVERRIDE; 67 | 68 | //! Reimplemented from QTabWidget::QTabBar to cache painting parameters 69 | void resizeEvent(QResizeEvent *) Q_DECL_OVERRIDE; 70 | //! Reimplemented from QTabWidget::QTabBar to implement hover/click status of buttons 71 | void mousePressEvent(QMouseEvent *) Q_DECL_OVERRIDE; 72 | void mouseMoveEvent(QMouseEvent *) Q_DECL_OVERRIDE; 73 | void mouseReleaseEvent(QMouseEvent *) Q_DECL_OVERRIDE; 74 | void leaveEvent(QEvent *) Q_DECL_OVERRIDE; 75 | 76 | //! Reimplemented from QTabWidget::QTabBar to enable/disable 'real' closable tabs. 77 | virtual void tabInserted(int index) Q_DECL_OVERRIDE; 78 | virtual void tabRemoved(int index) Q_DECL_OVERRIDE; 79 | 80 | ToolWindowManagerArea *m_area; 81 | 82 | bool m_tabsClosable; 83 | 84 | struct ButtonData 85 | { 86 | QRect rect; 87 | QIcon icon; 88 | bool clicked; 89 | bool hover; 90 | 91 | bool operator==(const ButtonData &o) 92 | { 93 | return rect == o.rect && clicked == o.clicked && hover == o.hover; 94 | } 95 | 96 | bool operator!=(const ButtonData &o) { return !(*this == o); } 97 | } m_close, m_pin; 98 | 99 | QRect m_titleRect; 100 | 101 | void updateClosable(); 102 | bool floatingWindowChild() const; 103 | }; 104 | 105 | #endif // TOOLWINDOWMANAGERTABBAR_H 106 | -------------------------------------------------------------------------------- /src/ToolWindowManagerWrapper.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014 Pavel Strakhov 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | * 24 | */ 25 | #include "ToolWindowManagerWrapper.h" 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include "ToolWindowManager.h" 36 | #include "ToolWindowManagerArea.h" 37 | 38 | ToolWindowManagerWrapper::ToolWindowManagerWrapper(ToolWindowManager *manager, bool floating) 39 | : QWidget(manager), m_manager(manager) 40 | { 41 | Qt::WindowFlags flags = Qt::Tool; 42 | 43 | #if defined(Q_OS_WIN32) 44 | flags = Qt::Dialog; 45 | flags |= Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowCloseButtonHint | 46 | Qt::WindowMaximizeButtonHint; 47 | #else 48 | flags |= Qt::FramelessWindowHint; 49 | #endif 50 | 51 | setMouseTracking(true); 52 | 53 | setWindowFlags(flags); 54 | setWindowTitle(QStringLiteral(" ")); 55 | 56 | m_dragReady = false; 57 | m_dragActive = false; 58 | m_dragDirection = ResizeDirection::Count; 59 | 60 | m_floating = floating; 61 | 62 | QVBoxLayout *mainLayout = new QVBoxLayout(this); 63 | mainLayout->setContentsMargins(0, 0, 0, 0); 64 | mainLayout->setMargin(0); 65 | mainLayout->setSpacing(0); 66 | m_manager->m_wrappers << this; 67 | 68 | m_moveTimeout = new QTimer(this); 69 | m_moveTimeout->setInterval(100); 70 | m_moveTimeout->stop(); 71 | QObject::connect(m_moveTimeout, &QTimer::timeout, this, &ToolWindowManagerWrapper::moveTimeout); 72 | 73 | m_closeButtonSize = 0; 74 | m_frameWidth = 0; 75 | m_titleHeight = 0; 76 | 77 | if(floating && (flags & Qt::FramelessWindowHint)) 78 | { 79 | m_closeButtonSize = style()->pixelMetric(QStyle::PM_SmallIconSize, 0, this); 80 | 81 | QFontMetrics titleFontMetrics = fontMetrics(); 82 | int mw = style()->pixelMetric(QStyle::PM_DockWidgetTitleMargin, 0, this); 83 | 84 | m_titleHeight = qMax(m_closeButtonSize + 2, titleFontMetrics.height() + 2 * mw); 85 | 86 | m_frameWidth = style()->pixelMetric(QStyle::PM_DockWidgetFrameWidth, 0, this); 87 | 88 | mainLayout->setContentsMargins(QMargins(m_frameWidth + 4, m_frameWidth + 4 + m_titleHeight, 89 | m_frameWidth + 4, m_frameWidth + 4)); 90 | } 91 | 92 | if(floating) 93 | { 94 | installEventFilter(this); 95 | updateTitle(); 96 | } 97 | } 98 | 99 | ToolWindowManagerWrapper::~ToolWindowManagerWrapper() 100 | { 101 | m_manager->m_wrappers.removeOne(this); 102 | } 103 | 104 | void ToolWindowManagerWrapper::updateTitle() 105 | { 106 | if(!m_floating) 107 | return; 108 | 109 | // find the best candidate for a 'title' for this floating window. 110 | if(layout()->count() > 0) 111 | { 112 | QWidget *child = layout()->itemAt(0)->widget(); 113 | 114 | while(child) 115 | { 116 | // if we've found an area, use its currently selected tab's text 117 | if(ToolWindowManagerArea *area = qobject_cast(child)) 118 | { 119 | setWindowTitle(area->tabText(area->currentIndex())); 120 | return; 121 | } 122 | // otherwise we should have a splitter 123 | if(QSplitter *splitter = qobject_cast(child)) 124 | { 125 | // if it's empty, just bail 126 | if(splitter->count() == 0) 127 | break; 128 | 129 | // if it's vertical, we pick the first child and recurse 130 | if(splitter->orientation() == Qt::Vertical) 131 | { 132 | child = splitter->widget(0); 133 | continue; 134 | } 135 | 136 | // if it's horizontal there's ambiguity so we just pick the biggest one by size, with a 137 | // tie-break for the leftmost one 138 | QList sizes = splitter->sizes(); 139 | int maxIdx = 0; 140 | int maxSize = sizes[0]; 141 | for(int i = 1; i < sizes.count(); i++) 142 | { 143 | if(sizes[i] > maxSize) 144 | { 145 | maxSize = sizes[i]; 146 | maxIdx = i; 147 | } 148 | } 149 | 150 | child = splitter->widget(maxIdx); 151 | continue; 152 | } 153 | 154 | // if not, use this object's window title 155 | setWindowTitle(child->windowTitle()); 156 | return; 157 | } 158 | } 159 | 160 | setWindowTitle(QStringLiteral("Tool Window")); 161 | } 162 | 163 | void ToolWindowManagerWrapper::closeEvent(QCloseEvent *event) 164 | { 165 | // abort dragging caused by QEvent::NonClientAreaMouseButtonPress in eventFilter function 166 | m_manager->abortDrag(); 167 | 168 | QList toolWindows; 169 | foreach(ToolWindowManagerArea *tabWidget, findChildren()) 170 | { 171 | if(ToolWindowManager::managerOf(tabWidget) == m_manager) 172 | { 173 | toolWindows << tabWidget->toolWindows(); 174 | } 175 | } 176 | 177 | foreach(QWidget *toolWindow, toolWindows) 178 | { 179 | if(!m_manager->allowClose(toolWindow)) 180 | { 181 | event->ignore(); 182 | return; 183 | } 184 | } 185 | 186 | foreach(QWidget *toolWindow, toolWindows) 187 | { 188 | if(m_manager->toolWindowProperties(toolWindow) & ToolWindowManager::HideOnClose) 189 | m_manager->hideToolWindow(toolWindow); 190 | else 191 | m_manager->removeToolWindow(toolWindow, true); 192 | } 193 | } 194 | 195 | bool ToolWindowManagerWrapper::eventFilter(QObject *object, QEvent *event) 196 | { 197 | const Qt::CursorShape shapes[(int)ResizeDirection::Count] = { 198 | Qt::SizeFDiagCursor, Qt::SizeBDiagCursor, Qt::SizeBDiagCursor, Qt::SizeFDiagCursor, 199 | Qt::SizeVerCursor, Qt::SizeHorCursor, Qt::SizeVerCursor, Qt::SizeHorCursor, 200 | }; 201 | 202 | if(object == this) 203 | { 204 | if(event->type() == QEvent::MouseButtonRelease || 205 | event->type() == QEvent::NonClientAreaMouseButtonRelease) 206 | { 207 | m_dragReady = false; 208 | m_dragDirection = ResizeDirection::Count; 209 | if(!m_dragActive && m_closeRect.contains(mapFromGlobal(QCursor::pos()))) 210 | { 211 | // catch clicks on the close button 212 | close(); 213 | } 214 | else 215 | { 216 | // if the mouse button is released, let the manager finish the drag and don't call any more 217 | // updates for any further move events 218 | m_dragActive = false; 219 | m_manager->updateDragPosition(); 220 | } 221 | } 222 | else if(event->type() == QEvent::MouseMove || event->type() == QEvent::NonClientAreaMouseMove) 223 | { 224 | // if we're ready to start a drag, check how far we've moved and start the drag if past a 225 | // certain pixel threshold. 226 | if(m_dragReady) 227 | { 228 | if((QCursor::pos() - m_dragStartCursor).manhattanLength() > 10) 229 | { 230 | m_dragActive = true; 231 | m_dragReady = false; 232 | QList toolWindows; 233 | foreach(ToolWindowManagerArea *tabWidget, findChildren()) 234 | { 235 | if(ToolWindowManager::managerOf(tabWidget) == m_manager) 236 | { 237 | toolWindows << tabWidget->toolWindows(); 238 | } 239 | } 240 | m_manager->startDrag(toolWindows, this); 241 | } 242 | } 243 | // if the drag is active, update it in the manager. 244 | if(m_dragActive) 245 | { 246 | m_manager->updateDragPosition(); 247 | 248 | // on non-windows we have no native title bar, so we need to move the window ourselves 249 | #if !defined(Q_OS_WIN32) 250 | move(QCursor::pos() - (m_dragStartCursor - m_dragStartGeometry.topLeft())); 251 | #endif 252 | } 253 | if(titleRect().contains(mapFromGlobal(QCursor::pos()))) 254 | { 255 | // if we're in the title bar, repaint to pick up motion over the close button 256 | update(); 257 | } 258 | 259 | ResizeDirection dir = checkResize(); 260 | 261 | if(m_dragDirection != ResizeDirection::Count) 262 | { 263 | dir = m_dragDirection; 264 | 265 | QRect g = geometry(); 266 | 267 | switch(dir) 268 | { 269 | case ResizeDirection::NW: g.setTopLeft(QCursor::pos()); break; 270 | case ResizeDirection::NE: g.setTopRight(QCursor::pos()); break; 271 | case ResizeDirection::SW: g.setBottomLeft(QCursor::pos()); break; 272 | case ResizeDirection::SE: g.setBottomRight(QCursor::pos()); break; 273 | case ResizeDirection::N: g.setTop(QCursor::pos().y()); break; 274 | case ResizeDirection::E: g.setRight(QCursor::pos().x()); break; 275 | case ResizeDirection::S: g.setBottom(QCursor::pos().y()); break; 276 | case ResizeDirection::W: g.setLeft(QCursor::pos().x()); break; 277 | case ResizeDirection::Count: break; 278 | } 279 | 280 | setGeometry(g); 281 | } 282 | 283 | if(dir != ResizeDirection::Count) 284 | { 285 | setCursor(shapes[(int)dir]); 286 | 287 | QObjectList children = this->children(); 288 | for(int i = 0; i < children.size(); ++i) 289 | { 290 | if(QWidget *w = qobject_cast(children.at(i))) 291 | { 292 | if(!w->testAttribute(Qt::WA_SetCursor)) 293 | { 294 | w->setCursor(Qt::ArrowCursor); 295 | } 296 | } 297 | } 298 | } 299 | else 300 | { 301 | unsetCursor(); 302 | } 303 | } 304 | else if(event->type() == QEvent::MouseButtonPress) 305 | { 306 | ResizeDirection dir = checkResize(); 307 | m_dragStartCursor = QCursor::pos(); 308 | m_dragStartGeometry = geometry(); 309 | if(dir == ResizeDirection::Count) 310 | m_dragReady = true; 311 | else 312 | m_dragDirection = dir; 313 | } 314 | else if(event->type() == QEvent::NonClientAreaMouseButtonPress) 315 | { 316 | m_dragActive = true; 317 | m_dragReady = false; 318 | m_dragStartCursor = QCursor::pos(); 319 | m_dragStartGeometry = geometry(); 320 | QList toolWindows; 321 | foreach(ToolWindowManagerArea *tabWidget, findChildren()) 322 | { 323 | if(ToolWindowManager::managerOf(tabWidget) == m_manager) 324 | { 325 | toolWindows << tabWidget->toolWindows(); 326 | } 327 | } 328 | m_manager->startDrag(toolWindows, this); 329 | } 330 | else if(event->type() == QEvent::Move && m_dragActive) 331 | { 332 | m_manager->updateDragPosition(); 333 | m_moveTimeout->start(); 334 | } 335 | else if(event->type() == QEvent::Leave) 336 | { 337 | unsetCursor(); 338 | } 339 | else if(event->type() == QEvent::MouseButtonDblClick && 340 | titleRect().contains(mapFromGlobal(QCursor::pos()))) 341 | { 342 | if(isMaximized()) 343 | { 344 | showNormal(); 345 | } 346 | else 347 | { 348 | showMaximized(); 349 | } 350 | } 351 | } 352 | return QWidget::eventFilter(object, event); 353 | } 354 | 355 | void ToolWindowManagerWrapper::paintEvent(QPaintEvent *) 356 | { 357 | if(!m_floating || m_titleHeight == 0) 358 | return; 359 | 360 | { 361 | QStylePainter p(this); 362 | 363 | QStyleOptionFrame frameOptions; 364 | frameOptions.init(this); 365 | p.drawPrimitive(QStyle::PE_FrameDockWidget, frameOptions); 366 | 367 | // Title must be painted after the frame, since the areas overlap, and 368 | // the title may wish to extend out to all sides (eg. XP style) 369 | QStyleOptionDockWidget titlebarOptions; 370 | 371 | titlebarOptions.initFrom(this); 372 | titlebarOptions.rect = titleRect(); 373 | titlebarOptions.title = windowTitle(); 374 | titlebarOptions.closable = true; 375 | titlebarOptions.movable = true; 376 | titlebarOptions.floatable = false; 377 | titlebarOptions.verticalTitleBar = false; 378 | 379 | p.drawControl(QStyle::CE_DockWidgetTitle, titlebarOptions); 380 | 381 | QStyleOptionToolButton buttonOpt; 382 | 383 | buttonOpt.initFrom(this); 384 | buttonOpt.iconSize = QSize(m_closeButtonSize, m_closeButtonSize); 385 | buttonOpt.subControls = 0; 386 | buttonOpt.activeSubControls = 0; 387 | buttonOpt.features = QStyleOptionToolButton::None; 388 | buttonOpt.arrowType = Qt::NoArrow; 389 | buttonOpt.state = QStyle::State_Active | QStyle::State_Enabled | QStyle::State_AutoRaise; 390 | 391 | if(m_closeRect.contains(mapFromGlobal(QCursor::pos()))) 392 | { 393 | buttonOpt.state |= QStyle::State_MouseOver | QStyle::State_Raised; 394 | } 395 | 396 | buttonOpt.rect = m_closeRect; 397 | buttonOpt.icon = m_closeIcon; 398 | 399 | if(style()->styleHint(QStyle::SH_DockWidget_ButtonsHaveFrame, 0, this)) 400 | { 401 | style()->drawPrimitive(QStyle::PE_PanelButtonTool, &buttonOpt, &p, this); 402 | } 403 | 404 | style()->drawComplexControl(QStyle::CC_ToolButton, &buttonOpt, &p, this); 405 | } 406 | } 407 | 408 | void ToolWindowManagerWrapper::resizeEvent(QResizeEvent *) 409 | { 410 | // abort dragging caused by QEvent::NonClientAreaMouseButtonPress in eventFilter function 411 | m_manager->abortDrag(); 412 | 413 | QStyleOptionDockWidget option; 414 | 415 | option.initFrom(this); 416 | option.rect = titleRect(); 417 | option.closable = true; 418 | option.movable = true; 419 | option.floatable = true; 420 | 421 | m_closeRect = style()->subElementRect(QStyle::SE_DockWidgetCloseButton, &option, this); 422 | m_closeIcon = style()->standardIcon(QStyle::SP_TitleBarCloseButton, &option, this); 423 | } 424 | 425 | QRect ToolWindowManagerWrapper::titleRect() 426 | { 427 | QRect ret; 428 | 429 | ret.setTopLeft(QPoint(m_frameWidth, m_frameWidth)); 430 | ret.setSize(QSize(geometry().width() - (m_frameWidth * 2), m_titleHeight)); 431 | 432 | return ret; 433 | } 434 | 435 | QVariantMap ToolWindowManagerWrapper::saveState() 436 | { 437 | if(layout()->count() > 2) 438 | { 439 | qWarning("too many children for wrapper"); 440 | return QVariantMap(); 441 | } 442 | if(isWindow() && layout()->count() == 0) 443 | { 444 | qWarning("empty top level wrapper"); 445 | return QVariantMap(); 446 | } 447 | QVariantMap result; 448 | result[QStringLiteral("geometry")] = QString::fromLatin1(saveGeometry().toBase64()); 449 | QSplitter *splitter = findChild(QString(), Qt::FindDirectChildrenOnly); 450 | if(splitter) 451 | { 452 | result[QStringLiteral("splitter")] = m_manager->saveSplitterState(splitter); 453 | } 454 | else 455 | { 456 | ToolWindowManagerArea *area = findChild(); 457 | if(area) 458 | { 459 | result[QStringLiteral("area")] = area->saveState(); 460 | } 461 | else if(layout()->count() > 0) 462 | { 463 | qWarning("unknown child"); 464 | return QVariantMap(); 465 | } 466 | } 467 | return result; 468 | } 469 | 470 | void ToolWindowManagerWrapper::restoreState(const QVariantMap &savedData) 471 | { 472 | restoreGeometry(QByteArray::fromBase64(savedData[QStringLiteral("geometry")].toByteArray())); 473 | if(layout()->count() > 1) 474 | { 475 | qWarning("wrapper is not empty"); 476 | return; 477 | } 478 | if(savedData.contains(QStringLiteral("splitter"))) 479 | { 480 | layout()->addWidget( 481 | m_manager->restoreSplitterState(savedData[QStringLiteral("splitter")].toMap())); 482 | } 483 | else if(savedData.contains(QStringLiteral("area"))) 484 | { 485 | ToolWindowManagerArea *area = m_manager->createArea(); 486 | area->restoreState(savedData[QStringLiteral("area")].toMap()); 487 | layout()->addWidget(area); 488 | } 489 | } 490 | 491 | void ToolWindowManagerWrapper::moveTimeout() 492 | { 493 | m_manager->updateDragPosition(); 494 | 495 | if(!m_manager->dragInProgress()) 496 | { 497 | m_moveTimeout->stop(); 498 | } 499 | } 500 | 501 | ToolWindowManagerWrapper::ResizeDirection ToolWindowManagerWrapper::checkResize() 502 | { 503 | if(m_titleHeight == 0) 504 | return ResizeDirection::Count; 505 | 506 | // check if we should offer to resize 507 | QRect rect = this->rect(); 508 | QPoint testPos = mapFromGlobal(QCursor::pos()); 509 | 510 | if(m_closeRect.contains(testPos)) 511 | return ResizeDirection::Count; 512 | 513 | const int resizeMargin = 4; 514 | 515 | if(rect.contains(testPos)) 516 | { 517 | // check corners first, then horizontal/vertical 518 | if(testPos.x() < rect.x() + resizeMargin * 4 && testPos.y() < rect.y() + resizeMargin * 4) 519 | { 520 | return ResizeDirection::NW; 521 | } 522 | else if(testPos.x() > rect.width() - resizeMargin * 4 && testPos.y() < rect.y() + resizeMargin * 4) 523 | { 524 | return ResizeDirection::NE; 525 | } 526 | else if(testPos.x() < rect.x() + resizeMargin * 4 && 527 | testPos.y() > rect.height() - resizeMargin * 4) 528 | { 529 | return ResizeDirection::SW; 530 | } 531 | else if(testPos.x() > rect.width() - resizeMargin * 4 && 532 | testPos.y() > rect.height() - resizeMargin * 4) 533 | { 534 | return ResizeDirection::SE; 535 | } 536 | else if(testPos.x() < rect.x() + resizeMargin) 537 | { 538 | return ResizeDirection::W; 539 | } 540 | else if(testPos.x() > rect.width() - resizeMargin) 541 | { 542 | return ResizeDirection::E; 543 | } 544 | else if(testPos.y() < rect.y() + resizeMargin) 545 | { 546 | return ResizeDirection::N; 547 | } 548 | else if(testPos.y() > rect.height() - resizeMargin) 549 | { 550 | return ResizeDirection::S; 551 | } 552 | } 553 | 554 | return ResizeDirection::Count; 555 | } 556 | -------------------------------------------------------------------------------- /src/ToolWindowManagerWrapper.h: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014 Pavel Strakhov 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | * 24 | */ 25 | #ifndef TOOLWINDOWMANAGERWRAPPER_H 26 | #define TOOLWINDOWMANAGERWRAPPER_H 27 | 28 | #include 29 | #include 30 | #include 31 | 32 | class ToolWindowManager; 33 | class QLabel; 34 | 35 | /*! 36 | * \brief The ToolWindowManagerWrapper class is used by ToolWindowManager to wrap its content. 37 | * One wrapper is a direct child of the manager and contains tool windows that are inside its 38 | * window. 39 | * All other wrappers are top level floating windows that contain detached tool windows. 40 | * 41 | */ 42 | class ToolWindowManagerWrapper : public QWidget 43 | { 44 | Q_OBJECT 45 | public: 46 | //! Creates new wrapper. 47 | explicit ToolWindowManagerWrapper(ToolWindowManager *manager, bool floating); 48 | //! Removes the wrapper. 49 | virtual ~ToolWindowManagerWrapper(); 50 | 51 | ToolWindowManager *manager() { return m_manager; } 52 | bool floating() { return m_floating; } 53 | void updateTitle(); 54 | 55 | protected: 56 | //! Reimplemented to register hiding of contained tool windows when user closes the floating 57 | //! window. 58 | virtual void closeEvent(QCloseEvent *) Q_DECL_OVERRIDE; 59 | 60 | //! Event filter for grabbing and processing mouse drags as toolwindow drags. 61 | virtual bool eventFilter(QObject *object, QEvent *event) Q_DECL_OVERRIDE; 62 | 63 | //! Painting and resizing for custom-rendered widget frames 64 | virtual void paintEvent(QPaintEvent *) Q_DECL_OVERRIDE; 65 | virtual void resizeEvent(QResizeEvent *) Q_DECL_OVERRIDE; 66 | 67 | private: 68 | ToolWindowManager *m_manager; 69 | 70 | enum class ResizeDirection 71 | { 72 | NW, 73 | NE, 74 | SW, 75 | SE, 76 | N, 77 | E, 78 | S, 79 | W, 80 | Count, 81 | }; 82 | 83 | QRect titleRect(); 84 | ResizeDirection checkResize(); 85 | 86 | QRect m_closeRect; 87 | QIcon m_closeIcon; 88 | int m_closeButtonSize; 89 | int m_titleHeight; 90 | int m_frameWidth; 91 | bool m_floating; 92 | 93 | QTimer *m_moveTimeout; 94 | 95 | bool m_dragReady; // we've clicked and started moving but haven't moved enough yet 96 | QPoint m_dragStartCursor; // cursor at the click to start a drag 97 | QRect m_dragStartGeometry; // window geometry at the click to start a drag 98 | bool m_dragActive; // whether a drag currently on-going 99 | ResizeDirection m_dragDirection; // the current direction being dragged 100 | 101 | // dump content's layout to variable 102 | QVariantMap saveState(); 103 | 104 | // construct layout based on given dump 105 | void restoreState(const QVariantMap &data); 106 | 107 | friend class ToolWindowManager; 108 | 109 | private slots: 110 | void moveTimeout(); 111 | }; 112 | 113 | #endif // TOOLWINDOWMANAGERWRAPPER_H 114 | --------------------------------------------------------------------------------