diff --git a/.github/workflows/linux-builds.yml b/.github/workflows/linux-builds.yml index 5f67e7f..4be2a29 100644 --- a/.github/workflows/linux-builds.yml +++ b/.github/workflows/linux-builds.yml @@ -16,6 +16,7 @@ jobs: run: | sudo apt-get update --fix-missing sudo apt-get install qt5-default + sudo apt-get install libqt5x11extras5-dev - name: qmake run: qmake - name: make diff --git a/.gitignore b/.gitignore index f9e79be..05aa783 100644 --- a/.gitignore +++ b/.gitignore @@ -382,3 +382,4 @@ MigrationBackup/ FodyWeavers.xsd / build /Settings.ini +.vscode/settings.json diff --git a/.travis.yml b/.travis.yml index d45b526..7e38344 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,8 @@ matrix: os: linux dist: trusty group: stable + before_install: + - sudo apt-get install libqt5x11extras5-dev addons: apt: sources: @@ -18,6 +20,7 @@ matrix: packages: - qt55base - qt55tools + - qt55x11extras - gcc-9 - g++-9 script: @@ -35,6 +38,8 @@ matrix: services: - xvfb compiler: gcc + before_install: + - sudo apt-get install libqt5x11extras5-dev addons: apt: sources: @@ -44,6 +49,7 @@ matrix: packages: - qt514base - qt514tools + - qt514x11extras - gcc-9 - g++-9 - libc6-i386 @@ -66,6 +72,8 @@ matrix: - xvfb compiler: gcc addons: + before_install: + - sudo apt-get install libqt5x11extras5-dev apt: sources: - ubuntu-toolchain-r-test @@ -74,6 +82,7 @@ matrix: packages: - qt514base - qt514tools + - qt514x11extras - gcc-9 - g++-9 - libc6-i386 @@ -95,6 +104,8 @@ matrix: services: - xvfb compiler: gcc + before_install: + - sudo apt-get install libqt5x11extras5-dev addons: apt: sources: @@ -104,6 +115,7 @@ matrix: packages: - qt514base - qt514tools + - qt514x11extras - gcc-9 - g++-9 - libc6-i386 @@ -131,6 +143,8 @@ matrix: services: - xvfb compiler: gcc + before_install: + - sudo apt-get install libqt5x11extras5-dev addons: apt: sources: @@ -140,6 +154,7 @@ matrix: packages: - qt514base - qt514tools + - qt514x11extras - gcc-9 - g++-9 - libc6-i386 diff --git a/README.md b/README.md index 73ec16a..c23768e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Advanced Docking System for Qt -[![Build Status](https://travis-ci.org/githubuser0xFFFF/Qt-Advanced-Docking-System.svg?branch=master)](https://travis-ci.org/githubuser0xFFFF/Qt-Advanced-Docking-System) +[![Build status](https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/workflows/linux-builds/badge.svg)](https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/actions?query=workflow%3Alinux-builds) [![Build status](https://ci.appveyor.com/api/projects/status/qcfb3cy932jw9mpy/branch/master?svg=true)](https://ci.appveyor.com/project/githubuser0xFFFF/qt-advanced-docking-system/branch/master) [![License: LGPL v2.1](https://img.shields.io/badge/License-LGPL%20v2.1-blue.svg)](gnu-lgpl-v2.1.md) @@ -59,6 +59,7 @@ know it from Visual Studio. - [QmixElements](#qmixelements) - [ezEditor](#ezeditor) - [D-Tect X](#d-tect-x) + - [HiveWE](#hivewe) ### Docking everywhere - no central widget @@ -312,7 +313,7 @@ If this project help you reduce time to develop or if you just like it, you can From version 4.12 on, Qt Creator uses the Advanced Docking Framework for its Qt Quick Designer. This improves the usability when using multiple screens. -![Qt Creator](doc/qtcreator.png) +![Qt Creator](doc/showcase_qtcreator.png) ### [Qt Design Studio](https://www.qt.io/ui-design-tools) @@ -320,7 +321,7 @@ Taken from the [Qt Blog](https://www.qt.io/blog/qt-design-studio-1.5-beta-releas > The most obvious change in [Qt Design Studio 1.5](https://www.qt.io/blog/qt-design-studio-1.5-beta-released) is the integration of dock widgets using the Qt Advanced Docking System. This allows the user to fully customize the workspace and also to undock any view into its own top level window. This especially improves the usability when using multiple screens. -![Qt Design Studio](doc/qt_design_studio.png) +![Qt Design Studio](doc/showcase_qt_design_studio.png) ### [QmixElements](https://www.cetoni.com/products/qmixelements/) @@ -328,7 +329,7 @@ The QmixElements software from [CETONI](https://www.cetoni.com) is a comprehensi plugin-based and modular laboratory automation software for controlling CETONI devices using a joint graphical user interface. The software features a powerful script system to automate processes. This [blog post](https://www.cetoni.com/blog/qmixelements-advanced-docking-system/) gives a nice overview about the use of the Qt Advanced Docking System in the QmixElements sofware. -![QmixElements](doc/qmix_elements.png) +![QmixElements](doc/showcase_qmix_elements.png) ### [ezEditor](https://github.com/ezEngine/ezEngine) @@ -336,7 +337,7 @@ The ezEditor is a full blown graphical editor used for editing scenes and importing and authoring assets for the [ezEngine](https://github.com/ezEngine/ezEngine) - an open source C++ game engine in active development. -![ezEditor](doc/ezEngine_editor.png) +![ezEditor](doc/showcase_ezEngine_editor.png) ### [D-Tect X](https://www.duerr-ndt.com/products/ndt-software/d-tect-xray-inspection-software.html) @@ -344,4 +345,16 @@ D-Tect X is a X-ray inspection software for industrial radiography. It is a stat [learn more...](https://www.duerr-ndt.com/products/ndt-software/d-tect-xray-inspection-software.html) -![D-TectX](doc/d-tect-x.jpg) +![D-TectX](doc/showcase_d-tect-x.jpg) + +### [HiveWE](https://github.com/stijnherfst/HiveWE) + +HiveWE is a Warcraft III world editor. It focusses on speed and ease of use, +especially for large maps where the regular World Editor is often too slow and clunky. +It has a JASS editor with syntax hightlighting, tabs, code completion and more. +The JASS editor uses the Qt Advanced Docking System for the management and layout +of the open editor windows. + +[learn more...](https://github.com/stijnherfst/HiveWE) + +![HiveWE](doc/showcase_hivewe.png) diff --git a/adsConfig.cmake b/adsConfig.cmake index 542e5c9..d006a3f 100644 --- a/adsConfig.cmake +++ b/adsConfig.cmake @@ -2,4 +2,5 @@ include(CMakeFindDependencyMacro) find_dependency(Qt5Core ${REQUIRED_QT_VERSION} REQUIRED) find_dependency(Qt5Gui ${REQUIRED_QT_VERSION} REQUIRED) find_dependency(Qt5Widgets ${REQUIRED_QT_VERSION} REQUIRED) +find_dependency(Qt5X11Extras ${REQUIRED_QT_VERSION} REQUIRED) include("${CMAKE_CURRENT_LIST_DIR}/adsTargets.cmake") \ No newline at end of file diff --git a/demo/MainWindow.cpp b/demo/MainWindow.cpp index a4cb640..5127242 100644 --- a/demo/MainWindow.cpp +++ b/demo/MainWindow.cpp @@ -400,8 +400,8 @@ void MainWindowPrivate::createContent() ads::CDockComponentsFactory::setFactory(new CCustomComponentsFactory()); auto TopDockArea = DockManager->addDockWidget(ads::TopDockWidgetArea, FileSystemWidget); // Uncomment the next line if you would like to test the - // setHideSingleWidgetTitleBar() functionality - // TopDockArea->setHideSingleWidgetTitleBar(true); + // HideSingleWidgetTitleBar functionality + // TopDockArea->setDockAreaFlag(ads::CDockAreaWidget::HideSingleWidgetTitleBar, true); ads::CDockComponentsFactory::resetDefaultFactory(); // We create a calendar widget and clear all flags to prevent the dock area diff --git a/demo/demo.py b/demo/demo.py index c35f687..ff268f6 100644 --- a/demo/demo.py +++ b/demo/demo.py @@ -202,6 +202,9 @@ class MainWindow(MainWindowUI, MainWindowBase): # Test custom factory - we inject a help button into the title bar QtAds.CDockComponentsFactory.setFactory(CCustomComponentsFactory()) top_dock_area = self.dock_manager.addDockWidget(QtAds.TopDockWidgetArea, file_system_widget) + # Uncomment the next line if you would like to test the + # setHideSingleWidgetTitleBar() functionality + # top_dock_area.setHideSingleWidgetTitleBar(True) QtAds.CDockComponentsFactory.resetDefaultFactory() # We create a calendar widget and clear all flags to prevent the dock area @@ -310,6 +313,7 @@ class MainWindow(MainWindowUI, MainWindowBase): def closeEvent(self, event: QCloseEvent): self.save_state() + self.dock_manager.deleteLater() super().closeEvent(event) def on_actionSaveState_triggered(self, state: bool): diff --git a/doc/d-tect-x.jpg b/doc/showcase_d-tect-x.jpg similarity index 100% rename from doc/d-tect-x.jpg rename to doc/showcase_d-tect-x.jpg diff --git a/doc/ezEngine_editor.png b/doc/showcase_ezEngine_editor.png similarity index 100% rename from doc/ezEngine_editor.png rename to doc/showcase_ezEngine_editor.png diff --git a/doc/showcase_hivewe.png b/doc/showcase_hivewe.png new file mode 100644 index 0000000..f7ba52b Binary files /dev/null and b/doc/showcase_hivewe.png differ diff --git a/doc/qmix_elements.png b/doc/showcase_qmix_elements.png similarity index 100% rename from doc/qmix_elements.png rename to doc/showcase_qmix_elements.png diff --git a/doc/qt_design_studio.png b/doc/showcase_qt_design_studio.png similarity index 100% rename from doc/qt_design_studio.png rename to doc/showcase_qt_design_studio.png diff --git a/doc/qtcreator.png b/doc/showcase_qtcreator.png similarity index 100% rename from doc/qtcreator.png rename to doc/showcase_qtcreator.png diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index d1a4516..32ca8c4 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -2,4 +2,5 @@ cmake_minimum_required(VERSION 3.5) project(QtADSExamples LANGUAGES CXX VERSION ${VERSION_SHORT}) add_subdirectory(simple) add_subdirectory(sidebar) -add_subdirectory(deleteonclose) \ No newline at end of file +add_subdirectory(deleteonclose) +add_subdirectory(centralwidget) \ No newline at end of file diff --git a/examples/centralwidget/CMakeLists.txt b/examples/centralwidget/CMakeLists.txt new file mode 100644 index 0000000..aa69603 --- /dev/null +++ b/examples/centralwidget/CMakeLists.txt @@ -0,0 +1,25 @@ +cmake_minimum_required(VERSION 3.5) +project(ads_example_centralwidget VERSION ${VERSION_SHORT}) +find_package(Qt5 5.5 COMPONENTS Core Gui Widgets REQUIRED) +set(CMAKE_INCLUDE_CURRENT_DIR ON) +add_executable(CentralWidgetExample WIN32 + main.cpp + mainwindow.cpp + mainwindow.ui +) +target_include_directories(CentralWidgetExample PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/../../src") +target_link_libraries(CentralWidgetExample PRIVATE qtadvanceddocking) +target_link_libraries(CentralWidgetExample PUBLIC Qt5::Core Qt5::Gui Qt5::Widgets) +set_target_properties(CentralWidgetExample PROPERTIES + AUTOMOC ON + AUTORCC ON + AUTOUIC ON + CXX_STANDARD 14 + CXX_STANDARD_REQUIRED ON + CXX_EXTENSIONS OFF + VERSION ${VERSION_SHORT} + EXPORT_NAME "Qt Advanced Docking System Central Widget Example" + ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${ads_PlatformDir}/lib" + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${ads_PlatformDir}/lib" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${ads_PlatformDir}/bin" +) diff --git a/examples/centralwidget/centralwidget.pro b/examples/centralwidget/centralwidget.pro new file mode 100644 index 0000000..3c0932d --- /dev/null +++ b/examples/centralwidget/centralwidget.pro @@ -0,0 +1,50 @@ +ADS_OUT_ROOT = $${OUT_PWD}/../.. + +QT += core gui widgets + +TARGET = CentralWidgetExample +DESTDIR = $${ADS_OUT_ROOT}/lib +TEMPLATE = app +CONFIG += c++14 +CONFIG += debug_and_release +adsBuildStatic { + DEFINES += ADS_STATIC +} + +# The following define makes your compiler emit warnings if you use +# any Qt feature that has been marked deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +SOURCES += \ + main.cpp \ + mainwindow.cpp + +HEADERS += \ + mainwindow.h + +FORMS += \ + mainwindow.ui + +LIBS += -L$${ADS_OUT_ROOT}/lib + +# Dependency: AdvancedDockingSystem (shared) +CONFIG(debug, debug|release){ + win32 { + LIBS += -lqtadvanceddockingd + } + else:mac { + LIBS += -lqtadvanceddocking_debug + } + else { + LIBS += -lqtadvanceddocking + } +} +else{ + LIBS += -lqtadvanceddocking +} + +INCLUDEPATH += ../../src +DEPENDPATH += ../../src + diff --git a/examples/centralwidget/main.cpp b/examples/centralwidget/main.cpp new file mode 100644 index 0000000..fa4c4fd --- /dev/null +++ b/examples/centralwidget/main.cpp @@ -0,0 +1,10 @@ +#include +#include + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + CMainWindow w; + w.show(); + return a.exec(); +} diff --git a/examples/centralwidget/mainwindow.cpp b/examples/centralwidget/mainwindow.cpp new file mode 100644 index 0000000..f4c7858 --- /dev/null +++ b/examples/centralwidget/mainwindow.cpp @@ -0,0 +1,83 @@ +#include "mainwindow.h" + +#include "ui_mainwindow.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "DockAreaWidget.h" +#include "DockAreaTitleBar.h" +#include "DockAreaTabBar.h" +#include "FloatingDockContainer.h" +#include "DockComponentsFactory.h" + +using namespace ads; + +CMainWindow::CMainWindow(QWidget *parent) + : QMainWindow(parent) + , ui(new Ui::CMainWindow) +{ + ui->setupUi(this); + CDockManager::setConfigFlag(CDockManager::OpaqueSplitterResize, true); + CDockManager::setConfigFlag(CDockManager::XmlCompressionEnabled, false); + DockManager = new CDockManager(this); + + // Set central widget + QCalendarWidget* calendar = new QCalendarWidget(); + CDockWidget* CentralDockWidget = new CDockWidget("CentralWidget"); + CentralDockWidget->setWidget(calendar); + auto* CentralDockArea = DockManager->setCentralWidget(CentralDockWidget); + CentralDockArea->setAllowedAreas(DockWidgetArea::OuterDockAreas); + + // create other dock widgets + QTreeView* fileTree = new QTreeView(); + fileTree->setFrameShape(QFrame::NoFrame); + QFileSystemModel* fileModel = new QFileSystemModel(fileTree); + fileModel->setRootPath(QDir::currentPath()); + fileTree->setModel(fileModel); + CDockWidget* DataDockWidget = new CDockWidget("File system"); + DataDockWidget->setWidget(fileTree); + DataDockWidget->resize(150, 250); + DataDockWidget->setMinimumSize(100, 250); + auto* fileArea = DockManager->addDockWidget(DockWidgetArea::LeftDockWidgetArea, DataDockWidget, CentralDockArea); + ui->menuView->addAction(DataDockWidget->toggleViewAction()); + + QTableWidget* table = new QTableWidget(); + table->setColumnCount(3); + table->setRowCount(10); + CDockWidget* TableDockWidget = new CDockWidget("Table"); + TableDockWidget->setWidget(table); + TableDockWidget->setMinimumSizeHintMode(CDockWidget::MinimumSizeHintFromDockWidget); + TableDockWidget->resize(250, 150); + TableDockWidget->setMinimumSize(200,150); + DockManager->addDockWidget(DockWidgetArea::BottomDockWidgetArea, TableDockWidget, fileArea); + ui->menuView->addAction(TableDockWidget->toggleViewAction()); + + QTableWidget* propertiesTable = new QTableWidget(); + table->setColumnCount(3); + table->setRowCount(10); + CDockWidget* PropertiesDockWidget = new CDockWidget("Properties"); + PropertiesDockWidget->setWidget(propertiesTable); + PropertiesDockWidget->setMinimumSizeHintMode(CDockWidget::MinimumSizeHintFromDockWidget); + PropertiesDockWidget->resize(250, 150); + PropertiesDockWidget->setMinimumSize(200,150); + DockManager->addDockWidget(DockWidgetArea::RightDockWidgetArea, PropertiesDockWidget, CentralDockArea); + ui->menuView->addAction(PropertiesDockWidget->toggleViewAction()); +} + +CMainWindow::~CMainWindow() +{ + delete ui; +} + diff --git a/examples/centralwidget/mainwindow.h b/examples/centralwidget/mainwindow.h new file mode 100644 index 0000000..cbd05b0 --- /dev/null +++ b/examples/centralwidget/mainwindow.h @@ -0,0 +1,32 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include + +#include "DockManager.h" +#include "DockAreaWidget.h" +#include "DockWidget.h" + +QT_BEGIN_NAMESPACE +namespace Ui { class CMainWindow; } +QT_END_NAMESPACE + +class CMainWindow : public QMainWindow +{ + Q_OBJECT + +public: + CMainWindow(QWidget *parent = nullptr); + ~CMainWindow(); + +private: + static const QString kTableTopLayout; + static const QString kTableBottomLayout; + + Ui::CMainWindow *ui; + + ads::CDockManager* DockManager; + ads::CDockAreaWidget* StatusDockArea; + ads::CDockWidget* TimelineDockWidget; +}; +#endif // MAINWINDOW_H diff --git a/examples/centralwidget/mainwindow.ui b/examples/centralwidget/mainwindow.ui new file mode 100644 index 0000000..135fdd1 --- /dev/null +++ b/examples/centralwidget/mainwindow.ui @@ -0,0 +1,36 @@ + + + CMainWindow + + + + 0 + 0 + 1284 + 757 + + + + MainWindow + + + + + + 0 + 0 + 1284 + 21 + + + + + View + + + + + + + + diff --git a/examples/deleteonclose/deleteonclose.py b/examples/deleteonclose/deleteonclose.py new file mode 100644 index 0000000..514f1a0 --- /dev/null +++ b/examples/deleteonclose/deleteonclose.py @@ -0,0 +1,73 @@ +import sys + +from PyQtAds import QtAds +from PyQt5.QtGui import QCloseEvent +from PyQt5.QtCore import (qDebug, pyqtSlot, QObject, pyqtSignal) +from PyQt5.QtWidgets import (QMainWindow, QAction, QTextEdit, QApplication, + QMenuBar) + + +class MainWindow(QMainWindow): + dock_manager = None + + def closeEvent(self, event: QCloseEvent): + super().closeEvent(event) + if self.dock_manager is not None: + self.dock_manager.deleteLater() + + def setDockManager(self, dock_manager: QtAds.CDockManager): + self.dock_manager = dock_manager + + +if __name__ == '__main__': + app = QApplication(sys.argv) + w = MainWindow() + + QtAds.CDockManager.setConfigFlag(QtAds.CDockManager.FocusHighlighting, True) + QtAds.CDockManager.setConfigFlag(QtAds.CDockManager.AllTabsHaveCloseButton, True) + dock_manager = QtAds.CDockManager(w) + w.setDockManager(dock_manager) + + count = 0 + + def on_focused_dock_widget_changed(old: QtAds.CDockWidget, now: QtAds.CDockWidget): + global count + qDebug( "{:d} CDockManager::focusedDockWidgetChanged old: {} now: {} visible: {}".format( + count, + old.objectName() if old else "-", + now.objectName(), + now.isVisible())) + count += 1 + now.widget().setFocus() + + dock_manager.focusedDockWidgetChanged.connect(on_focused_dock_widget_changed) + + action = QAction("New Delete On Close", w) + w.menuBar().addAction(action) + + i = 0 + def on_action_triggered(): + global i + dw = QtAds.CDockWidget("test doc {:d}".format(i)) + i += 1 + editor = QTextEdit("lorem ipsum...", dw) + dw.setWidget(editor) + dw.setFeature(QtAds.CDockWidget.DockWidgetDeleteOnClose, True) + area = dock_manager.addDockWidgetTab(QtAds.CenterDockWidgetArea, dw) + qDebug("doc dock widget created! {} {}".format(dw, area)) + action.triggered.connect(on_action_triggered) + + action = QAction("New", w) + w.menuBar().addAction(action) + def on_action2_triggered(): + global i + dw = QtAds.CDockWidget("test {:d}".format(i)) + i += 1 + editor = QTextEdit("lorem ipsum...", dw) + dw.setWidget(editor) + area = dock_manager.addDockWidgetTab(QtAds.CenterDockWidgetArea, dw) + qDebug("dock widget created! {} {}".format(dw, area)) + action.triggered.connect(on_action2_triggered) + + w.show() + app.exec_() \ No newline at end of file diff --git a/examples/examples.pro b/examples/examples.pro index 68419c9..de8cf7f 100644 --- a/examples/examples.pro +++ b/examples/examples.pro @@ -1,6 +1,7 @@ TEMPLATE = subdirs SUBDIRS = \ + centralwidget \ simple \ sidebar \ deleteonclose diff --git a/examples/simple/MainWindow.cpp b/examples/simple/MainWindow.cpp index 6779b3a..46e24b3 100644 --- a/examples/simple/MainWindow.cpp +++ b/examples/simple/MainWindow.cpp @@ -9,7 +9,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { - /*ui->setupUi(this); + ui->setupUi(this); // Create the dock manager. Because the parent parameter is a QMainWindow // the dock manager registers itself as the central widget. @@ -32,67 +32,7 @@ MainWindow::MainWindow(QWidget *parent) : ui->menuView->addAction(DockWidget->toggleViewAction()); // Add the dock widget to the top dock widget area - m_DockManager->addDockWidget(ads::TopDockWidgetArea, DockWidget);*/ - - ui->setupUi(this); - - // Create the dock manager. Because the parent parameter is a QMainWindow - // the dock manager registers itself as the central widget. - m_DockManager1 = new ads::CDockManager(this); - - // Create example content label - this can be any application specific - // widget - ads::CDockWidget* DockWidget; - { - QLabel* l = new QLabel(); - l->setWordWrap(true); - l->setAlignment(Qt::AlignTop | Qt::AlignLeft); - l->setText("Lorem ipsum dolor sit amet, consectetuer adipiscing elit. "); - - // Create a dock widget with the title Label 1 and set the created label - // as the dock widget content - DockWidget = new ads::CDockWidget("Label 1"); - DockWidget->setWidget(l); - } - - ads::CDockWidget* DockWidget2; - { - QLabel* l = new QLabel(); - l->setWordWrap(true); - l->setAlignment(Qt::AlignTop | Qt::AlignLeft); - l->setText("Lorem ipsum dolor sit amet, consectetuer adipiscing elit. "); - - // Create a dock widget with the title Label 1 and set the created label - // as the dock widget content - DockWidget2 = new ads::CDockWidget("Label 2"); - DockWidget2->setWidget(l); - } - - - // Add the toggleViewAction of the dock widget to the menu to give - // the user the possibility to show the dock widget if it has been closed - ui->menuView->addAction(DockWidget->toggleViewAction()); - - // Add the dock widget to the top dock widget area - m_DockManager1->addDockWidget(ads::TopDockWidgetArea, DockWidget); - - - auto funcRemoveFirstManager = [=]() - { - m_DockManager1->removeDockWidget(DockWidget); - - delete m_DockManager1; - m_DockManager1 = nullptr; - }; - QTimer::singleShot(3000, funcRemoveFirstManager); - - auto funcAddSecondManager = [=]() - { - m_DockManager2 = new ads::CDockManager(this); - - m_DockManager2->addDockWidget(ads::TopDockWidgetArea, DockWidget); - }; - QTimer::singleShot(5000, funcAddSecondManager); + m_DockManager->addDockWidget(ads::TopDockWidgetArea, DockWidget); } MainWindow::~MainWindow() @@ -100,17 +40,3 @@ MainWindow::~MainWindow() delete ui; } - -void MainWindow::closeEvent(QCloseEvent *event) -{ - QMainWindow::closeEvent(event); - if (m_DockManager1) - { - m_DockManager1->deleteLater(); - } - - if (m_DockManager2) - { - m_DockManager2->deleteLater(); - } -} diff --git a/examples/simple/MainWindow.h b/examples/simple/MainWindow.h index e1c6176..8d90885 100644 --- a/examples/simple/MainWindow.h +++ b/examples/simple/MainWindow.h @@ -18,13 +18,9 @@ public: explicit MainWindow(QWidget *parent = 0); ~MainWindow(); -protected: - virtual void closeEvent(QCloseEvent *event) override; - private: Ui::MainWindow *ui; - ads::CDockManager* m_DockManager1; - ads::CDockManager* m_DockManager2; + ads::CDockManager* m_DockManager; }; #endif // MAINWINDOW_H diff --git a/examples/simple/simple.py b/examples/simple/simple.py index 20c6fd2..fe24fc6 100644 --- a/examples/simple/simple.py +++ b/examples/simple/simple.py @@ -2,7 +2,8 @@ import os import sys from PyQt5 import uic -from PyQt5.QtCore import Qt +from PyQt5.QtCore import Qt, QTimer +from PyQt5.QtGui import QCloseEvent from PyQt5.QtWidgets import QApplication, QLabel from PyQtAds import QtAds @@ -20,7 +21,7 @@ class MainWindow(MainWindowUI, MainWindowBase): # Create the dock manager. Because the parent parameter is a QMainWindow # the dock manager registers itself as the central widget. - self.dock_manager = QtAds.CDockManager(self) + self.dock_manager1 = QtAds.CDockManager(self) # Create example content label - this can be any application specific # widget @@ -33,13 +34,42 @@ class MainWindow(MainWindowUI, MainWindowBase): # as the dock widget content dock_widget = QtAds.CDockWidget("Label 1") dock_widget.setWidget(l) + + l = QLabel() + l.setWordWrap(True) + l.setAlignment(Qt.AlignTop | Qt.AlignLeft); + l.setText("Lorem ipsum dolor sit amet, consectetuer adipiscing elit. ") + + # Create a dock widget with the title Label 1 and set the created label + # as the dock widget content + dock_widget2 = QtAds.CDockWidget("Label 2") + dock_widget2.setWidget(l) # Add the toggleViewAction of the dock widget to the menu to give # the user the possibility to show the dock widget if it has been closed self.menuView.addAction(dock_widget.toggleViewAction()) # Add the dock widget to the top dock widget area - self.dock_manager.addDockWidget(QtAds.TopDockWidgetArea, dock_widget) + self.dock_manager1.addDockWidget(QtAds.TopDockWidgetArea, dock_widget) + + def remove_first_manager(): + self.dock_manager1.removeDockWidget(dock_widget) + del self.dock_manager1 + QTimer.singleShot(3000, remove_first_manager) + + def add_second_manager(): + self.dock_manager2 = QtAds.CDockManager(self) + self.dock_manager2.addDockWidget(QtAds.TopDockWidgetArea, dock_widget) + QTimer.singleShot(5000, add_second_manager) + + def closeEvent(self, event: QCloseEvent): + super().closeEvent(event) + + if hasattr(self, 'dock_manager1'): + self.dock_manager1.deleteLater() + + if hasattr(self, 'dock_manager2'): + self.dock_manager2.deleteLater() if __name__ == '__main__': diff --git a/sip/DockAreaWidget.sip b/sip/DockAreaWidget.sip index dbe1c7e..f7f1e4a 100644 --- a/sip/DockAreaWidget.sip +++ b/sip/DockAreaWidget.sip @@ -51,6 +51,7 @@ public: void setAllowedAreas(DockWidgetAreas areas); DockWidgetAreas allowedAreas() const; + void setHideSingleWidgetTitleBar(bool hide); CDockAreaTitleBar* titleBar() const; public slots: diff --git a/sip/DockManager.sip b/sip/DockManager.sip index fb5a3fa..d5d2f7a 100644 --- a/sip/DockManager.sip +++ b/sip/DockManager.sip @@ -1,7 +1,7 @@ %Import QtWidgets/QtWidgetsmod.sip %If (Qt_5_0_0 -) - + %MappedType QMap /TypeHint="Dict[QString, CDockWidget*]", TypeHintValue="{}"/ { @@ -224,11 +224,11 @@ signals: void stateRestored(); void openingPerspective(const QString& PerspectiveName); void perspectiveOpened(const QString& PerspectiveName); - void floatingWidgetCreated(CFloatingDockContainer* FloatingWidget); - void dockAreaCreated(ads::CDockAreaWidget* DockArea); - void dockWidgetAboutToBeRemoved(ads::CDockWidget* DockWidget); - void dockWidgetRemoved(ads::CDockWidget* DockWidget); - void focusedDockWidgetChanged(ads::CDockWidget* old, ads::CDockWidget* now); + void floatingWidgetCreated(ads::CFloatingDockContainer*); + void dockAreaCreated(ads::CDockAreaWidget*); + void dockWidgetAboutToBeRemoved(ads::CDockWidget*); + void dockWidgetRemoved(ads::CDockWidget*); + void focusedDockWidgetChanged(ads::CDockWidget*, ads::CDockWidget*); }; }; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4531cc1..6e31d27 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.5) project(QtAdvancedDockingSystem LANGUAGES CXX VERSION ${VERSION_SHORT}) -find_package(Qt5 5.5 COMPONENTS Core Gui Widgets REQUIRED) +find_package(Qt5 5.5 COMPONENTS Core Gui Widgets X11Extras REQUIRED) set(CMAKE_INCLUDE_CURRENT_DIR ON) if(BUILD_STATIC) set(CMAKE_STATIC_LIBRARY_SUFFIX "_static${CMAKE_STATIC_LIBRARY_SUFFIX}") diff --git a/src/DockAreaWidget.cpp b/src/DockAreaWidget.cpp index e45921e..10046a5 100644 --- a/src/DockAreaWidget.cpp +++ b/src/DockAreaWidget.cpp @@ -175,6 +175,7 @@ public: { LayoutItem->widget()->setParent(nullptr); } + delete LayoutItem; m_ParentLayout->addWidget(next); if (prev) @@ -235,6 +236,7 @@ public: using DockAreaLayout = CDockAreaLayout; +static const DockWidgetAreas DefaultAllowedAreas = AllDockAreas; /** @@ -248,9 +250,9 @@ struct DockAreaWidgetPrivate CDockAreaTitleBar* TitleBar = nullptr; CDockManager* DockManager = nullptr; bool UpdateTitleBarButtons = false; - DockWidgetAreas AllowedAreas = AllDockAreas; - bool HideSingleWidgetTitleBar = false; + DockWidgetAreas AllowedAreas = DefaultAllowedAreas; QSize MinSizeHint; + CDockAreaWidget::DockAreaFlags Flags{CDockAreaWidget::DefaultFlags}; /** * Private data constructor @@ -439,6 +441,7 @@ void CDockAreaWidget::insertDockWidget(int index, CDockWidget* DockWidget, DockWidget->toggleViewInternal(true); } d->updateTitleBarButtonStates(); + updateTitleBarVisibility(); } @@ -465,6 +468,14 @@ void CDockAreaWidget::removeDockWidget(CDockWidget* DockWidget) ADS_PRINT("Dock Area empty"); DockContainer->removeDockArea(this); this->deleteLater(); + if(DockContainer->dockAreaCount() == 0) + { + if(CFloatingDockContainer* FloatingDockContainer = DockContainer->floatingWidget()) + { + FloatingDockContainer->hide(); + FloatingDockContainer->deleteLater(); + } + } } else if (DockWidget == CurrentDockWidget) { @@ -746,7 +757,7 @@ void CDockAreaWidget::updateTitleBarVisibility() { bool Hidden = Container->hasTopLevelDockWidget() && (Container->isFloating() || CDockManager::testConfigFlag(CDockManager::HideSingleCentralWidgetTitleBar)); - Hidden |= (d->HideSingleWidgetTitleBar && openDockWidgetsCount() == 1); + Hidden |= (d->Flags.testFlag(HideSingleWidgetTitleBar) && openDockWidgetsCount() == 1); d->TitleBar->setVisible(!Hidden); } } @@ -771,6 +782,17 @@ void CDockAreaWidget::saveState(QXmlStreamWriter& s) const auto CurrentDockWidget = currentDockWidget(); QString Name = CurrentDockWidget ? CurrentDockWidget->objectName() : ""; s.writeAttribute("Current", Name); + // To keep the saved XML data small, we only save the allowed areas and the + // dock area flags if the values are different from the default values + if (d->AllowedAreas != DefaultAllowedAreas) + { + s.writeAttribute("AllowedAreas", QString::number(d->AllowedAreas, 16)); + } + + if (d->Flags != DefaultFlags) + { + s.writeAttribute("Flags", QString::number(d->Flags, 16)); + } ADS_PRINT("CDockAreaWidget::saveState TabCount: " << d->ContentsLayout->count() << " Current: " << Name); for (int i = 0; i < d->ContentsLayout->count(); ++i) @@ -864,11 +886,32 @@ DockWidgetAreas CDockAreaWidget::allowedAreas() const return d->AllowedAreas; } + //============================================================================ -void CDockAreaWidget::setHideSingleWidgetTitleBar(bool hide) +CDockAreaWidget::DockAreaFlags CDockAreaWidget::dockAreaFlags() const { - d->HideSingleWidgetTitleBar = hide; - updateTitleBarVisibility(); + return d->Flags; +} + + +//============================================================================ +void CDockAreaWidget::setDockAreaFlags(DockAreaFlags Flags) +{ + auto ChangedFlags = d->Flags ^ Flags; + d->Flags = Flags; + if (ChangedFlags.testFlag(HideSingleWidgetTitleBar)) + { + updateTitleBarVisibility(); + } +} + + +//============================================================================ +void CDockAreaWidget::setDockAreaFlag(eDockAreaFlag Flag, bool On) +{ + auto flags = dockAreaFlags(); + internal::setFlag(flags, Flag, On); + setDockAreaFlags(flags); } @@ -916,11 +959,35 @@ CDockAreaTitleBar* CDockAreaWidget::titleBar() const } +//============================================================================ +bool CDockAreaWidget::isCentralWidgetArea() const +{ + if (dockWidgetsCount()!= 1) + { + return false; + } + + return dockManager()->centralWidget() == dockWidgets()[0]; +} + + //============================================================================ QSize CDockAreaWidget::minimumSizeHint() const { return d->MinSizeHint.isValid() ? d->MinSizeHint : Super::minimumSizeHint(); } + + +//============================================================================ +void CDockAreaWidget::onDockWidgetFeaturesChanged() +{ + if (d->TitleBar) + { + d->updateTitleBarButtonStates(); + } +} + + } // namespace ads //--------------------------------------------------------------------------- diff --git a/src/DockAreaWidget.h b/src/DockAreaWidget.h index 3deada4..ac7dcb5 100644 --- a/src/DockAreaWidget.h +++ b/src/DockAreaWidget.h @@ -65,6 +65,7 @@ private: friend class CDockWidget; friend struct DockManagerPrivate; friend class CDockManager; + void onDockWidgetFeaturesChanged(); private slots: void onTabCloseRequested(int Index); @@ -144,6 +145,16 @@ public: using Super = QFrame; /** + * Dock area related flags + */ + enum eDockAreaFlag + { + HideSingleWidgetTitleBar = 0x0001, + DefaultFlags = 0x0000 + }; + Q_DECLARE_FLAGS(DockAreaFlags, eDockAreaFlag) + + /** * Default Constructor */ CDockAreaWidget(CDockManager* DockManager, CDockContainerWidget* parent); @@ -272,17 +283,35 @@ public: */ DockWidgetAreas allowedAreas() const; - /** - * Will hide the title bar when set to true and there is only one - * dock widget in this area - */ - void setHideSingleWidgetTitleBar(bool hide); - /** * Returns the title bar of this dock area */ CDockAreaTitleBar* titleBar() const; + /** + * Returns the dock area flags - a combination of flags that configure the + * appearance and features of the dock area. + * \see setDockAreaFlasg() + */ + DockAreaFlags dockAreaFlags() const; + + /** + * Sets the dock area flags - a combination of flags that configure the + * appearance and features of the dock area + */ + void setDockAreaFlags(DockAreaFlags Flags); + + /** + * Sets the dock area flag Flag on this widget if on is true; otherwise + * clears the flag. + */ + void setDockAreaFlag(eDockAreaFlag Flag, bool On); + + /** + * Returns true if the area contains the central widget of it's manager. + */ + bool isCentralWidgetArea() const; + public slots: /** * This activates the tab for the given tab index. diff --git a/src/DockContainerWidget.cpp b/src/DockContainerWidget.cpp index e0bc72c..a10495e 100644 --- a/src/DockContainerWidget.cpp +++ b/src/DockContainerWidget.cpp @@ -322,6 +322,18 @@ public: Splitter->setSizes(SplitterSizes); } + /** + * This function forces the dock container widget to update handles of splitters + * based if a central widget exists. + */ + void updateSplitterHandles(QSplitter* splitter); + + /** + * If no central widget exists, the widgets resize with the container. + * If a central widget exists, the widgets surrounding the central widget + * do not resize its height or width. + */ + bool widgetResizesWithContainer(QWidget* widget); // private slots: ------------------------------------------------------------ void onDockAreaViewToggled(bool Visible) @@ -421,7 +433,8 @@ void DockContainerWidgetPrivate::dropIntoContainer(CFloatingDockContainer* Float QSplitter* NewSplitter = newSplitter(InsertParam.orientation()); QLayoutItem* li = Layout->replaceWidget(Splitter, NewSplitter); NewSplitter->addWidget(Splitter); - Splitter = NewSplitter; + updateSplitterHandles(NewSplitter); + Splitter = NewSplitter; delete li; } @@ -430,19 +443,21 @@ void DockContainerWidgetPrivate::dropIntoContainer(CFloatingDockContainer* Float if (FloatingSplitter->count() == 1) { insertWidgetIntoSplitter(Splitter, FloatingSplitter->widget(0), InsertParam.append()); - } + updateSplitterHandles(Splitter); + } else if (FloatingSplitter->orientation() == InsertParam.orientation()) { int InsertIndex = InsertParam.append() ? Splitter->count() : 0; while (FloatingSplitter->count()) { Splitter->insertWidget(InsertIndex++, FloatingSplitter->widget(0)); - } - } + updateSplitterHandles(Splitter); + } + } else { insertWidgetIntoSplitter(Splitter, FloatingSplitter, InsertParam.append()); - } + } RootSplitter = Splitter; addDockAreasToList(NewDockAreas); @@ -453,7 +468,7 @@ void DockContainerWidgetPrivate::dropIntoContainer(CFloatingDockContainer* Float if (!Splitter->isVisible()) { Splitter->show(); - } + } _this->dumpLayout(); } @@ -515,7 +530,8 @@ void DockContainerWidgetPrivate::dropIntoSection(CFloatingDockContainer* Floatin QSplitter* Splitter = newSplitter(InsertParam.orientation()); Layout->replaceWidget(TargetArea, Splitter); Splitter->addWidget(TargetArea); - TargetAreaSplitter = Splitter; + updateSplitterHandles(Splitter); + TargetAreaSplitter = Splitter; } int AreaIndex = TargetAreaSplitter->indexOf(TargetArea); auto Widget = FloatingWidget->dockContainer()->findChild(QString(), Qt::FindDirectChildrenOnly); @@ -529,7 +545,8 @@ void DockContainerWidgetPrivate::dropIntoSection(CFloatingDockContainer* Floatin if ((FloatingSplitter->orientation() != InsertParam.orientation()) && FloatingSplitter->count() > 1) { TargetAreaSplitter->insertWidget(AreaIndex + InsertParam.insertOffset(), Widget); - } + updateSplitterHandles(TargetAreaSplitter); + } else { AdjustSplitterSizes = (FloatingSplitter->count() == 1); @@ -537,8 +554,9 @@ void DockContainerWidgetPrivate::dropIntoSection(CFloatingDockContainer* Floatin while (FloatingSplitter->count()) { TargetAreaSplitter->insertWidget(InsertIndex++, FloatingSplitter->widget(0)); - } - } + updateSplitterHandles(TargetAreaSplitter); + } + } if (AdjustSplitterSizes) { @@ -557,28 +575,32 @@ void DockContainerWidgetPrivate::dropIntoSection(CFloatingDockContainer* Floatin if ((FloatingSplitter->orientation() != InsertParam.orientation()) && FloatingSplitter->count() > 1) { NewSplitter->addWidget(Widget); - } + updateSplitterHandles(NewSplitter); + } else { AdjustSplitterSizes = (FloatingSplitter->count() == 1); while (FloatingSplitter->count()) { NewSplitter->addWidget(FloatingSplitter->widget(0)); - } - } + updateSplitterHandles(NewSplitter); + } + } // Save the sizes before insertion and restore it later to prevent // shrinking of existing area auto Sizes = TargetAreaSplitter->sizes(); insertWidgetIntoSplitter(NewSplitter, TargetArea, !InsertParam.append()); - if (AdjustSplitterSizes) + updateSplitterHandles(NewSplitter); + if (AdjustSplitterSizes) { int Size = TargetAreaSize / 2; NewSplitter->setSizes({Size, Size}); } TargetAreaSplitter->insertWidget(AreaIndex, NewSplitter); TargetAreaSplitter->setSizes(Sizes); - } + updateSplitterHandles(TargetAreaSplitter); + } addDockAreasToList(NewDockAreas); _this->dumpLayout(); @@ -663,7 +685,8 @@ void DockContainerWidgetPrivate::moveToNewSection(QWidget* Widget, CDockAreaWidg { int TargetAreaSize = (InsertParam.orientation() == Qt::Horizontal) ? TargetArea->width() : TargetArea->height(); TargetAreaSplitter->insertWidget(AreaIndex + InsertParam.insertOffset(), NewDockArea); - int Size = (TargetAreaSize - TargetAreaSplitter->handleWidth()) / 2; + updateSplitterHandles(TargetAreaSplitter); + int Size = (TargetAreaSize - TargetAreaSplitter->handleWidth()) / 2; Sizes[AreaIndex] = Size; Sizes.insert(AreaIndex, Size); } @@ -674,16 +697,58 @@ void DockContainerWidgetPrivate::moveToNewSection(QWidget* Widget, CDockAreaWidg QSplitter* NewSplitter = newSplitter(InsertParam.orientation()); NewSplitter->addWidget(TargetArea); insertWidgetIntoSplitter(NewSplitter, NewDockArea, InsertParam.append()); - int Size = TargetAreaSize / 2; + updateSplitterHandles(NewSplitter); + int Size = TargetAreaSize / 2; NewSplitter->setSizes({Size, Size}); TargetAreaSplitter->insertWidget(AreaIndex, NewSplitter); - } + updateSplitterHandles(TargetAreaSplitter); + } TargetAreaSplitter->setSizes(Sizes); addDockAreasToList({NewDockArea}); } +//============================================================================ +void DockContainerWidgetPrivate::updateSplitterHandles( QSplitter* splitter ) +{ + if (!DockManager->centralWidget() || !splitter) + { + return; + } + + for (int i = 0; i < splitter->count(); ++i) + { + splitter->setStretchFactor(i, widgetResizesWithContainer(splitter->widget(i)) ? 1 : 0); + } +} + + +//============================================================================ +bool DockContainerWidgetPrivate::widgetResizesWithContainer(QWidget* widget) +{ + if (!DockManager->centralWidget()) + { + return true; + } + + auto Area = qobject_cast(widget); + if(Area) + { + return Area->isCentralWidgetArea(); + } + + auto innerSplitter = qobject_cast(widget); + if (innerSplitter) + { + return innerSplitter->isResizingWithContainer(); + } + + return false; +} + + + //============================================================================ void DockContainerWidgetPrivate::moveToContainer(QWidget* Widget, DockWidgetArea area) { @@ -892,6 +957,10 @@ bool DockContainerWidgetPrivate::restoreSplitter(CDockingStateReader& s, Splitter->addWidget(ChildNode); Visible |= ChildNode->isVisibleTo(Splitter); } + if(!Testing) + { + updateSplitterHandles(Splitter); + } if (Sizes.count() != WidgetCount) { @@ -942,6 +1011,17 @@ bool DockContainerWidgetPrivate::restoreDockArea(CDockingStateReader& s, if (!Testing) { DockArea = new CDockAreaWidget(DockManager, _this); + const auto AllowedAreasAttribute = s.attributes().value("AllowedAreas"); + if (!AllowedAreasAttribute.isEmpty()) + { + DockArea->setAllowedAreas((DockWidgetArea)AllowedAreasAttribute.toInt(nullptr, 16)); + } + + const auto FlagsAttribute = s.attributes().value("Flags"); + if (!FlagsAttribute.isEmpty()) + { + DockArea->setDockAreaFlags((CDockAreaWidget::DockAreaFlags)FlagsAttribute.toInt(nullptr, 16)); + } } while (s.readNextStartElement()) @@ -1058,7 +1138,8 @@ void DockContainerWidgetPrivate::addDockArea(CDockAreaWidget* NewDockArea, DockW if (Splitter->orientation() == InsertParam.orientation()) { insertWidgetIntoSplitter(Splitter, NewDockArea, InsertParam.append()); - if (Splitter->isHidden()) + updateSplitterHandles(Splitter); + if (Splitter->isHidden()) { Splitter->show(); } @@ -1071,14 +1152,16 @@ void DockContainerWidgetPrivate::addDockArea(CDockAreaWidget* NewDockArea, DockW QLayoutItem* li = Layout->replaceWidget(Splitter, NewSplitter); NewSplitter->addWidget(Splitter); NewSplitter->addWidget(NewDockArea); - delete li; + updateSplitterHandles(NewSplitter); + delete li; } else { NewSplitter->addWidget(NewDockArea); QLayoutItem* li = Layout->replaceWidget(Splitter, NewSplitter); NewSplitter->addWidget(Splitter); - delete li; + updateSplitterHandles(NewSplitter); + delete li; } RootSplitter = NewSplitter; } @@ -1164,7 +1247,8 @@ CDockAreaWidget* DockContainerWidgetPrivate::addDockWidgetToDockArea(DockWidgetA { ADS_PRINT("TargetAreaSplitter->orientation() == InsertParam.orientation()"); TargetAreaSplitter->insertWidget(index + InsertParam.insertOffset(), NewDockArea); - // do nothing, if flag is not enabled + updateSplitterHandles(TargetAreaSplitter); + // do nothing, if flag is not enabled if (CDockManager::testConfigFlag(CDockManager::EqualSplitOnInsertion)) { adjustSplitterSizesOnInsertion(TargetAreaSplitter); @@ -1179,9 +1263,11 @@ CDockAreaWidget* DockContainerWidgetPrivate::addDockWidgetToDockArea(DockWidgetA NewSplitter->addWidget(TargetDockArea); insertWidgetIntoSplitter(NewSplitter, NewDockArea, InsertParam.append()); - TargetAreaSplitter->insertWidget(index, NewSplitter); - if (CDockManager::testConfigFlag(CDockManager::EqualSplitOnInsertion)) - { + updateSplitterHandles(NewSplitter); + TargetAreaSplitter->insertWidget(index, NewSplitter); + updateSplitterHandles(TargetAreaSplitter); + if (CDockManager::testConfigFlag(CDockManager::EqualSplitOnInsertion)) + { TargetAreaSplitter->setSizes(TargetAreaSizes); adjustSplitterSizesOnInsertion(NewSplitter); } @@ -1370,9 +1456,11 @@ void CDockContainerWidget::removeDockArea(CDockAreaWidget* area) } delete Splitter; + Splitter = nullptr; emitAndExit: - CDockWidget* TopLevelWidget = topLevelDockWidget(); + updateSplitterHandles(Splitter); + CDockWidget* TopLevelWidget = topLevelDockWidget(); // Updated the title bar visibility of the dock widget if there is only // one single visible dock widget @@ -1721,6 +1809,13 @@ QList CDockContainerWidget::dockWidgets() const } +//============================================================================ +void CDockContainerWidget::updateSplitterHandles(QSplitter* splitter) +{ + d->updateSplitterHandles(splitter); +} + + //============================================================================ CDockWidget::DockWidgetFeatures CDockContainerWidget::features() const { diff --git a/src/DockContainerWidget.h b/src/DockContainerWidget.h index 8f1b04c..0bc674b 100644 --- a/src/DockContainerWidget.h +++ b/src/DockContainerWidget.h @@ -155,6 +155,12 @@ protected: */ QList dockWidgets() const; + /** + * This function forces the dock container widget to update handles of splitters + * based on resize modes of dock widgets contained in the container. + */ + void updateSplitterHandles(QSplitter* splitter); + public: /** * Default Constructor @@ -276,7 +282,7 @@ signals: * This signal is emitted if a dock area is opened or closed via * toggleView() function */ - void dockAreaViewToggled(CDockAreaWidget* DockArea, bool Open); + void dockAreaViewToggled(ads::CDockAreaWidget* DockArea, bool Open); }; // class DockContainerWidget } // namespace ads //----------------------------------------------------------------------------- diff --git a/src/DockFocusController.cpp b/src/DockFocusController.cpp index c4a9332..c488288 100644 --- a/src/DockFocusController.cpp +++ b/src/DockFocusController.cpp @@ -83,6 +83,10 @@ static void updateDockAreaFocusStyle(CDockAreaWidget* DockArea, bool Focused) #ifdef Q_OS_LINUX static void updateFloatingWidgetFocusStyle(CFloatingDockContainer* FloatingWidget, bool Focused) { + if (FloatingWidget->hasNativeTitleBar()) + { + return; + } auto TitleBar = qobject_cast(FloatingWidget->titleBarWidget()); if (!TitleBar) { diff --git a/src/DockManager.cpp b/src/DockManager.cpp index 95e4fac..d322dfe 100644 --- a/src/DockManager.cpp +++ b/src/DockManager.cpp @@ -108,6 +108,7 @@ struct DockManagerPrivate bool RestoringState = false; QVector UninitializedFloatingWidgets; CDockFocusController* FocusController = nullptr; + CDockWidget* CentralWidget = nullptr; /** * Private data constructor @@ -296,11 +297,11 @@ bool DockManagerPrivate::restoreStateFromXml(const QByteArray &state, int versi { // Delete remaining empty floating widgets int FloatingWidgetIndex = DockContainerCount - 1; - int DeleteCount = FloatingWidgets.count() - FloatingWidgetIndex; - for (int i = 0; i < DeleteCount; ++i) + for (int i = FloatingWidgetIndex; i < FloatingWidgets.count(); ++i) { - FloatingWidgets[FloatingWidgetIndex + i]->deleteLater(); - _this->removeDockContainer(FloatingWidgets[FloatingWidgetIndex + i]->dockContainer()); + auto* floatingWidget = FloatingWidgets[i]; + _this->removeDockContainer(floatingWidget->dockContainer()); + floatingWidget->deleteLater(); } } @@ -367,8 +368,6 @@ void DockManagerPrivate::restoreDockAreasIndices() } } - - //============================================================================ void DockManagerPrivate::emitTopLevelEvents() { @@ -406,6 +405,7 @@ bool DockManagerPrivate::restoreState(const QByteArray& State, int version) return false; } + CentralWidget = nullptr; // Hide updates of floating widgets from use hideFloatingWidgets(); markDockWidgetsDirty(); @@ -474,6 +474,10 @@ CDockManager::CDockManager(QWidget *parent) : { d->FocusController = new CDockFocusController(this); } + +#ifdef Q_OS_LINUX + window()->installEventFilter(this); +#endif } //============================================================================ @@ -487,6 +491,70 @@ CDockManager::~CDockManager() delete d; } +//============================================================================ +#ifdef Q_OS_LINUX +bool CDockManager::eventFilter(QObject *obj, QEvent *e) +{ + // Emulate Qt:Tool behaviour. + // Required because on some WMs Tool windows can't be maximized. + + // Window always on top of the MainWindow. + if (e->type() == QEvent::WindowActivate) + { + for (auto _window : floatingWidgets()) + { + if (!_window->isVisible() || window()->isMinimized()) + { + continue; + } + // setWindowFlags(Qt::WindowStaysOnTopHint) will hide the window and thus requires a show call. + // This then leads to flickering and a nasty endless loop (also buggy behaviour on Ubuntu). + // So we just do it ourself. + internal::xcb_update_prop(true, _window->window()->winId(), + "_NET_WM_STATE", "_NET_WM_STATE_ABOVE", "_NET_WM_STATE_STAYS_ON_TOP"); + } + } + else if (e->type() == QEvent::WindowDeactivate) + { + for (auto _window : floatingWidgets()) + { + if (!_window->isVisible() || window()->isMinimized()) + { + continue; + } + internal::xcb_update_prop(false, _window->window()->winId(), + "_NET_WM_STATE", "_NET_WM_STATE_ABOVE", "_NET_WM_STATE_STAYS_ON_TOP"); + _window->raise(); + } + } + + // Sync minimize with MainWindow + if (e->type() == QEvent::WindowStateChange) + { + for (auto _window : floatingWidgets()) + { + if (! _window->isVisible()) + { + continue; + } + + if (window()->isMinimized()) + { + _window->showMinimized(); + } + else + { + _window->setWindowState(_window->windowState() & (~Qt::WindowMinimized)); + } + } + if (!window()->isMinimized()) + { + QApplication::setActiveWindow(window()); + } + } + return Super::eventFilter(obj, e); +} +#endif //============================================================================ void CDockManager::registerFloatingWidget(CFloatingDockContainer* FloatingWidget) @@ -816,6 +884,40 @@ void CDockManager::loadPerspectives(QSettings& Settings) Settings.endArray(); } + +//============================================================================ +CDockWidget* CDockManager::centralWidget() const +{ + return d->CentralWidget; +} + + +//============================================================================ +CDockAreaWidget* CDockManager::setCentralWidget(CDockWidget* widget) +{ + if (!widget) + { + d->CentralWidget = nullptr; + return nullptr; + } + + // Setting a new central widget is now allowed if there is alread a central + // widget + if (d->CentralWidget) + { + return nullptr; + } + + + widget->setFeature(CDockWidget::DockWidgetClosable, false); + widget->setFeature(CDockWidget::DockWidgetMovable, false); + widget->setFeature(CDockWidget::DockWidgetFloatable, false); + d->CentralWidget = widget; + CDockAreaWidget* CentralArea = addDockWidget(CenterDockWidgetArea, widget); + CentralArea->setDockAreaFlag(CDockAreaWidget::eDockAreaFlag::HideSingleWidgetTitleBar, true); + return CentralArea; +} + //============================================================================ QAction* CDockManager::addToggleViewActionToMenu(QAction* ToggleViewAction, const QString& Group, const QIcon& GroupIcon) diff --git a/src/DockManager.h b/src/DockManager.h index 6b5b72c..a987990 100644 --- a/src/DockManager.h +++ b/src/DockManager.h @@ -178,9 +178,17 @@ public: FloatingContainerHasWidgetIcon = 0x80000, //!< If set, the Floating Widget icon reflects the icon of the current dock widget otherwise it displays application icon HideSingleCentralWidgetTitleBar = 0x100000, //!< If there is only one single visible dock widget in the main dock container (the dock manager) and if this flag is set, then the titlebar of this dock widget will be hidden //!< this only makes sense for non draggable and non floatable widgets and enables the creation of some kind of "central" widget + FocusHighlighting = 0x200000, //!< enables styling of focused dock widget tabs or floating widget titlebar EqualSplitOnInsertion = 0x400000, ///!< if enabled, the space is equally distributed to all widgets in a splitter + FloatingContainerForceNativeTitleBar = 0x800000, //!< Linux only ! Forces all FloatingContainer to use the native title bar. This might break docking for FloatinContainer on some Window Managers (like Kwin/KDE). + //!< If neither this nor FloatingContainerForceCustomTitleBar is set (the default) native titlebars are used except on known bad systems. + //! Users can overwrite this by setting the environment variable ADS_UseNativeTitle to "1" or "0". + FloatingContainerForceQWidgetTitleBar = 0x1000000,//!< Linux only ! Forces all FloatingContainer to use a QWidget based title bar. + //!< If neither this nor FloatingContainerForceNativeTitleBar is set (the default) native titlebars are used except on known bad systems. + //! Users can overwrite this by setting the environment variable ADS_UseNativeTitle to "1" or "0". + DefaultDockAreaButtons = DockAreaHasCloseButton | DockAreaHasUndockButton | DockAreaHasTabsMenuButton,///< default configuration of dock area title bar buttons @@ -378,7 +386,28 @@ public: */ void loadPerspectives(QSettings& Settings); - /** + /** + * This function returns managers central widget or nullptr if no central widget is set. + */ + CDockWidget* centralWidget() const; + + /** + * Adds dockwidget widget into the central area and marks it as central widget. + * If central widget is set, it will be the only dock widget + * that will resize with the dock container. A central widget if not + * movable, floatable or closable and the titlebar of the central + * dock area is not visible. + * If the given widget could be set as central widget, the function returns + * the created cok area. If the widget could not be set, because there + * is already a central widget, this function returns a nullptr. + * To clear the central widget, pass a nullptr to the function. + * \retval != 0 The dock area that contains the central widget + * \retval nullptr Indicates that the given widget can not be set as central + * widget because there is already a central widget. + */ + CDockAreaWidget* setCentralWidget(CDockWidget* widget); + + /** * Adds a toggle view action to the the internal view menu. * You can either manage the insertion of the toggle view actions in your * application or you can add the actions to the internal view menu and @@ -443,6 +472,10 @@ public: widget->setFocus(Qt::OtherFocusReason); } +#ifdef Q_OS_LINUX + bool eventFilter(QObject *obj, QEvent *e) override; +#endif + public slots: /** * Opens the perspective with the given name. @@ -502,20 +535,20 @@ signals: * An application can use this signal to e.g. subscribe to events of * the newly created window. */ - void floatingWidgetCreated(CFloatingDockContainer* FloatingWidget); + void floatingWidgetCreated(ads::CFloatingDockContainer* FloatingWidget); /** * This signal is emitted, if a new DockArea has been created. * An application can use this signal to set custom icons or custom * tooltips for the DockArea buttons. */ - void dockAreaCreated(CDockAreaWidget* DockArea); + void dockAreaCreated(ads::CDockAreaWidget* DockArea); /** * This signal is emitted just before the given dock widget is removed - * from the + * from the dock manager */ - void dockWidgetAboutToBeRemoved(CDockWidget* DockWidget); + void dockWidgetAboutToBeRemoved(ads::CDockWidget* DockWidget); /** * This signal is emitted if a dock widget has been removed with the remove @@ -523,14 +556,14 @@ signals: * If this signal is emitted, the dock widget has been removed from the * docking system but it is not deleted yet. */ - void dockWidgetRemoved(CDockWidget* DockWidget); + void dockWidgetRemoved(ads::CDockWidget* DockWidget); /** * This signal is emitted if the focused dock widget changed. * Both old and now can be nullptr. * The focused dock widget is the one that is highlighted in the GUI */ - void focusedDockWidgetChanged(CDockWidget* old, CDockWidget* now); + void focusedDockWidgetChanged(ads::CDockWidget* old, ads::CDockWidget* now); }; // class DockManager } // namespace ads //----------------------------------------------------------------------------- diff --git a/src/DockSplitter.cpp b/src/DockSplitter.cpp index 92f988d..047f0f4 100644 --- a/src/DockSplitter.cpp +++ b/src/DockSplitter.cpp @@ -102,6 +102,20 @@ QWidget* CDockSplitter::lastWidget() const return (count() > 0) ? widget(count() - 1) : nullptr; } +//============================================================================ +bool CDockSplitter::isResizingWithContainer() const +{ + for (auto area : findChildren()) + { + if(area->isCentralWidgetArea()) + { + return true; + } + } + + return false; +} + } // namespace ads //--------------------------------------------------------------------------- diff --git a/src/DockSplitter.h b/src/DockSplitter.h index 24abc40..1cc766c 100644 --- a/src/DockSplitter.h +++ b/src/DockSplitter.h @@ -71,6 +71,11 @@ public: * Returns last widget of nullptr is splitter is empty */ QWidget* lastWidget() const; + + /** + * Returns true if the splitter contains central widget of dock manager. + */ + bool isResizingWithContainer() const; }; // class CDockSplitter } // namespace ads diff --git a/src/DockWidget.cpp b/src/DockWidget.cpp index 56c697e..8b66218 100644 --- a/src/DockWidget.cpp +++ b/src/DockWidget.cpp @@ -340,6 +340,8 @@ void CDockWidget::setFeatures(DockWidgetFeatures features) d->Features = features; emit featuresChanged(d->Features); d->TabWidget->onDockWidgetFeaturesChanged(); + if(CDockAreaWidget* DockArea = dockAreaWidget()) + DockArea->onDockWidgetFeaturesChanged(); } @@ -461,6 +463,13 @@ void CDockWidget::setMinimumSizeHintMode(eMinimumSizeHintMode Mode) } +//============================================================================ +bool CDockWidget::isCentralWidget() const +{ + return dockManager()->centralWidget() == this; +} + + //============================================================================ void CDockWidget::toggleView(bool Open) { @@ -540,6 +549,7 @@ void CDockWidget::setDockArea(CDockAreaWidget* DockArea) { d->DockArea = DockArea; d->ToggleViewAction->setChecked(DockArea != nullptr && !this->isClosed()); + setParent(DockArea); } diff --git a/src/DockWidget.h b/src/DockWidget.h index ca4360e..3c12fe9 100644 --- a/src/DockWidget.h +++ b/src/DockWidget.h @@ -149,10 +149,10 @@ public: { DockWidgetClosable = 0x01,///< dock widget has a close button DockWidgetMovable = 0x02,///< dock widget is movable and can be moved to a new position in the current dock container - DockWidgetFloatable = 0x04, + DockWidgetFloatable = 0x04,///< dock widget can be dragged into a floating window DockWidgetDeleteOnClose = 0x08, ///< deletes the dock widget when it is closed - CustomCloseHandling = 0x10, - DockWidgetFocusable = 0x20, + CustomCloseHandling = 0x10, ///< clicking the close button will not close the dock widget but emits the closeRequested() signal instead + DockWidgetFocusable = 0x20, ///< if this is enabled, a dock widget can get focus highlighting DockWidgetForceCloseWithArea = 0x40, DefaultDockWidgetFeatures = DockWidgetClosable | DockWidgetMovable | DockWidgetFloatable | DockWidgetFocusable, AllDockWidgetFeatures = DefaultDockWidgetFeatures | DockWidgetDeleteOnClose | CustomCloseHandling, @@ -198,7 +198,7 @@ public: * To ensure, that a dock widget does not block resizing, the dock widget * reimplements minimumSizeHint() function to return a very small minimum * size hint. If you would like to adhere the minimumSizeHint() from the - * content widget, the set the minimumSizeHintMode() to + * content widget, then set the minimumSizeHintMode() to * MinimumSizeHintFromContent. */ enum eMinimumSizeHintMode @@ -361,6 +361,11 @@ public: */ void setMinimumSizeHintMode(eMinimumSizeHintMode Mode); + /** + * Returns true if the dock widget is set as central widget of it's dock manager + */ + bool isCentralWidget() const; + /** * Sets the dock widget icon that is shown in tabs and in toggle view * actions @@ -578,7 +583,7 @@ signals: * This signal is emitted when the features property changes. * The features parameter gives the new value of the property. */ - void featuresChanged(DockWidgetFeatures features); + void featuresChanged(ads::CDockWidget::DockWidgetFeatures features); }; // class DockWidget } // namespace ads diff --git a/src/DockWidgetTab.cpp b/src/DockWidgetTab.cpp index d8b9a84..6d80b1f 100644 --- a/src/DockWidgetTab.cpp +++ b/src/DockWidgetTab.cpp @@ -134,6 +134,18 @@ struct DockWidgetTabPrivate } } + /** + * Update the close button visibility from current feature/config + */ + void updateCloseButtonVisibility(bool active) + { + bool DockWidgetClosable = DockWidget->features().testFlag(CDockWidget::DockWidgetClosable); + bool ActiveTabHasCloseButton = testConfigFlag(CDockManager::ActiveTabHasCloseButton); + bool AllTabsHaveCloseButton = testConfigFlag(CDockManager::AllTabsHaveCloseButton); + bool TabHasCloseButton = (ActiveTabHasCloseButton && active) | AllTabsHaveCloseButton; + CloseButton->setVisible(DockWidgetClosable && TabHasCloseButton); + } + template IFloatingWidget* createFloatingWidget(T* Widget, bool OpaqueUndocking) { @@ -461,11 +473,7 @@ bool CDockWidgetTab::isActiveTab() const //============================================================================ void CDockWidgetTab::setActiveTab(bool active) { - bool DockWidgetClosable = d->DockWidget->features().testFlag(CDockWidget::DockWidgetClosable); - bool ActiveTabHasCloseButton = d->testConfigFlag(CDockManager::ActiveTabHasCloseButton); - bool AllTabsHaveCloseButton = d->testConfigFlag(CDockManager::AllTabsHaveCloseButton); - bool TabHasCloseButton = (ActiveTabHasCloseButton && active) | AllTabsHaveCloseButton; - d->CloseButton->setVisible(DockWidgetClosable && TabHasCloseButton); + d->updateCloseButtonVisibility(active); // Focus related stuff if (CDockManager::testConfigFlag(CDockManager::FocusHighlighting) && !d->DockWidget->dockManager()->isRestoringState()) @@ -653,6 +661,7 @@ void CDockWidgetTab::onDockWidgetFeaturesChanged() SizePolicy.setRetainSizeWhenHidden(Features.testFlag(CDockWidget::DockWidgetClosable) && d->testConfigFlag(CDockManager::RetainTabSizeWhenCloseButtonHidden)); d->CloseButton->setSizePolicy(SizePolicy); + d->updateCloseButtonVisibility(isActiveTab()); } diff --git a/src/FloatingDockContainer.cpp b/src/FloatingDockContainer.cpp index 13c175a..09b31bc 100644 --- a/src/FloatingDockContainer.cpp +++ b/src/FloatingDockContainer.cpp @@ -376,6 +376,7 @@ struct FloatingDockContainerPrivate #ifdef Q_OS_LINUX QWidget* MouseEventHandler = nullptr; CFloatingWidgetTitleBar* TitleBar = nullptr; + bool IsResizing = false; #endif /** @@ -410,10 +411,12 @@ struct FloatingDockContainerPrivate void setWindowTitle(const QString &Text) { #ifdef Q_OS_LINUX - TitleBar->setTitle(Text); -#else - _this->setWindowTitle(Text); + if (TitleBar) + { + TitleBar->setTitle(Text); + } #endif + _this->setWindowTitle(Text); } /** @@ -604,13 +607,52 @@ CFloatingDockContainer::CFloatingDockContainer(CDockManager *DockManager) : SLOT(onDockAreasAddedOrRemoved())); #ifdef Q_OS_LINUX - d->TitleBar = new CFloatingWidgetTitleBar(this); - setWindowFlags(windowFlags() | Qt::Tool); - QDockWidget::setWidget(d->DockContainer); - QDockWidget::setFloating(true); - QDockWidget::setFeatures(QDockWidget::AllDockWidgetFeatures); - setTitleBarWidget(d->TitleBar); - connect(d->TitleBar, SIGNAL(closeRequested()), SLOT(close())); + QDockWidget::setWidget(d->DockContainer); + QDockWidget::setFloating(true); + QDockWidget::setFeatures(QDockWidget::AllDockWidgetFeatures); + + bool native_window = true; + + // FloatingContainerForce*TitleBar is overwritten by the "ADS_UseNativeTitle" environment variable if set. + auto env = qgetenv("ADS_UseNativeTitle").toUpper(); + if (env == "1") + { + native_window = true; + } + else if (env == "0") + { + native_window = false; + } + else if (DockManager->testConfigFlag(CDockManager::FloatingContainerForceNativeTitleBar)) + { + native_window = true; + } + else if (DockManager->testConfigFlag(CDockManager::FloatingContainerForceQWidgetTitleBar)) + { + native_window = false; + } + else + { + // KDE doesn't seem to fire MoveEvents while moving windows, so for now no native titlebar for everything using KWin. + QString window_manager = internal::windowManager().toUpper().split(" ")[0]; + native_window = window_manager != "KWIN"; + } + + if (native_window) + { + setTitleBarWidget(new QWidget()); + setWindowFlags(Qt::Window | Qt::WindowMaximizeButtonHint | Qt::CustomizeWindowHint | Qt::WindowCloseButtonHint); + } + else + { + d->TitleBar = new CFloatingWidgetTitleBar(this); + setTitleBarWidget(d->TitleBar); + setWindowFlags(Qt::Window | Qt::WindowMinMaxButtonsHint | Qt::FramelessWindowHint); + d->TitleBar->enableCloseButton(isClosable()); + connect(d->TitleBar, SIGNAL(closeRequested()), SLOT(close())); + connect(d->TitleBar, &CFloatingWidgetTitleBar::maximizeRequested, + this, &CFloatingDockContainer::onMaximizeRequest); + } #else setWindowFlags( Qt::Window | Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint); @@ -629,9 +671,7 @@ CFloatingDockContainer::CFloatingDockContainer(CDockAreaWidget *DockArea) : CFloatingDockContainer(DockArea->dockManager()) { d->DockContainer->addDockArea(DockArea); -#ifdef Q_OS_LINUX - d->TitleBar->enableCloseButton(isClosable()); -#endif + auto TopLevelDockWidget = topLevelDockWidget(); if (TopLevelDockWidget) { @@ -646,9 +686,6 @@ CFloatingDockContainer::CFloatingDockContainer(CDockWidget *DockWidget) : CFloatingDockContainer(DockWidget->dockManager()) { d->DockContainer->addDockWidget(CenterDockWidgetArea, DockWidget); -#ifdef Q_OS_LINUX - d->TitleBar->enableCloseButton(isClosable()); -#endif auto TopLevelDockWidget = topLevelDockWidget(); if (TopLevelDockWidget) { @@ -678,12 +715,19 @@ CDockContainerWidget* CFloatingDockContainer::dockContainer() const //============================================================================ void CFloatingDockContainer::changeEvent(QEvent *event) { - QWidget::changeEvent(event); + Super::changeEvent(event); if ((event->type() == QEvent::ActivationChange) && isActiveWindow()) { ADS_PRINT("FloatingWidget::changeEvent QEvent::ActivationChange "); d->zOrderIndex = ++zOrderCounter; - return; + +#ifdef Q_OS_LINUX + if (d->DraggingState == DraggingFloatingWidget) + { + d->titleMouseReleaseEvent(); + d->DraggingState = DraggingInactive; + } +#endif } } @@ -822,25 +866,35 @@ void CFloatingDockContainer::showEvent(QShowEvent *event) void CFloatingDockContainer::startFloating(const QPoint &DragStartMousePos, const QSize &Size, eDragState DragState, QWidget *MouseEventHandler) { -#ifndef Q_OS_LINUX - Q_UNUSED(MouseEventHandler) -#endif - resize(Size); - d->setState(DragState); - d->DragStartMousePosition = DragStartMousePos; #ifdef Q_OS_LINUX + if (!isMaximized()) + { + resize(Size); + d->DragStartMousePosition = DragStartMousePos; + } + d->setState(DragState); if (DraggingFloatingWidget == DragState) { - setAttribute(Qt::WA_X11NetWmWindowTypeDock, true); d->MouseEventHandler = MouseEventHandler; if (d->MouseEventHandler) { d->MouseEventHandler->grabMouse(); } } -#endif + + if (!isMaximized()) + { + moveFloating(); + } + show(); +#else + Q_UNUSED(MouseEventHandler) + resize(Size); + d->DragStartMousePosition = DragStartMousePos; + d->setState(DragState); moveFloating(); show(); +#endif } //============================================================================ @@ -850,7 +904,6 @@ void CFloatingDockContainer::moveFloating() const QPoint moveToPos = QCursor::pos() - d->DragStartMousePosition - QPoint(BorderSize, 0); move(moveToPos); - switch (d->DraggingState) { case DraggingMousePressed: @@ -949,11 +1002,17 @@ bool CFloatingDockContainer::restoreState(CDockingStateReader &Stream, { return false; } - onDockAreasAddedOrRemoved(); +#ifdef Q_OS_LINUX + if(d->TitleBar) + { + d->TitleBar->setMaximizedIcon(windowState() == Qt::WindowMaximized); + } +#endif return true; } + //============================================================================ bool CFloatingDockContainer::hasTopLevelDockWidget() const { @@ -977,19 +1036,17 @@ void CFloatingDockContainer::finishDragging() { ADS_PRINT("CFloatingDockContainer::finishDragging"); #ifdef Q_OS_LINUX - setAttribute(Qt::WA_X11NetWmWindowTypeDock, false); - setWindowOpacity(1); - activateWindow(); - if (d->MouseEventHandler) - { - d->MouseEventHandler->releaseMouse(); - d->MouseEventHandler = nullptr; - } + setWindowOpacity(1); + activateWindow(); + if (d->MouseEventHandler) + { + d->MouseEventHandler->releaseMouse(); + d->MouseEventHandler = nullptr; + } #endif - d->titleMouseReleaseEvent(); + d->titleMouseReleaseEvent(); } - #ifdef Q_OS_MACOS //============================================================================ bool CFloatingDockContainer::event(QEvent *e) @@ -1092,6 +1149,97 @@ void CFloatingDockContainer::moveEvent(QMoveEvent *event) } #endif + +#ifdef Q_OS_LINUX +//============================================================================ +void CFloatingDockContainer::onMaximizeRequest() +{ + if (windowState() == Qt::WindowMaximized) + { + showNormal(); + } + else + { + showMaximized(); + } +} + + +//============================================================================ +void CFloatingDockContainer::showNormal(bool fixGeometry) +{ + if (windowState() == Qt::WindowMaximized) + { + QRect oldNormal = normalGeometry(); + Super::showNormal(); + if(fixGeometry) + { + setGeometry(oldNormal); + } + } + if(d->TitleBar) + { + d->TitleBar->setMaximizedIcon(false); + } +} + + +//============================================================================ +void CFloatingDockContainer::showMaximized() +{ + Super::showMaximized(); + if (d->TitleBar) + { + d->TitleBar->setMaximizedIcon(true); + } +} + + +//============================================================================ +bool CFloatingDockContainer::isMaximized() const +{ + return windowState() == Qt::WindowMaximized; +} + + +//============================================================================ +void CFloatingDockContainer::show() +{ + // Prevent this window from showing in the taskbar and pager (alt+tab) + internal::xcb_add_prop(true, winId(), "_NET_WM_STATE", "_NET_WM_STATE_SKIP_TASKBAR"); + internal::xcb_add_prop(true, winId(), "_NET_WM_STATE", "_NET_WM_STATE_SKIP_PAGER"); + Super::show(); +} + + +//============================================================================ +void CFloatingDockContainer::resizeEvent(QResizeEvent *event) +{ + d->IsResizing = true; + Super::resizeEvent(event); +} + + +//============================================================================ +void CFloatingDockContainer::moveEvent(QMoveEvent *event) +{ + Super::moveEvent(event); + if (!d->IsResizing && event->spontaneous()) + { + d->DraggingState = DraggingFloatingWidget; + d->updateDropOverlays(QCursor::pos()); + } + d->IsResizing = false; +} + + +//============================================================================ +bool CFloatingDockContainer::hasNativeTitleBar() +{ + return d->TitleBar == nullptr; +} +#endif + } // namespace ads //--------------------------------------------------------------------------- diff --git a/src/FloatingDockContainer.h b/src/FloatingDockContainer.h index 0099e2a..a22d394 100644 --- a/src/FloatingDockContainer.h +++ b/src/FloatingDockContainer.h @@ -185,6 +185,11 @@ protected: // reimplements QWidget virtual void moveEvent(QMoveEvent *event) override; #endif +#ifdef Q_OS_LINUX + virtual void moveEvent(QMoveEvent *event) override; + virtual void resizeEvent(QResizeEvent *event) override; +#endif + #ifdef Q_OS_WIN /** * Native event filter for handling WM_MOVING messages on Windows @@ -194,7 +199,7 @@ protected: // reimplements QWidget public: - using Super = QWidget; + using Super = tFloatingWidgetBase; /** * Create empty floating widget - required for restore state @@ -248,6 +253,44 @@ public: * function of the internal container widget. */ QList dockWidgets() const; + +#ifdef Q_OS_LINUX + /** + * This is a function that responds to FloatingWidgetTitleBar::maximizeRequest() + * Maximize or normalize the container size. + */ + void onMaximizeRequest(); + + /** + * Normalize (Unmaximize) the window. + * fixGeometry parameter fixes a "bug" in QT where immediately after calling showNormal + * geometry is not set properly. + * Set this true when moving the window immediately after normalizing. + */ + void showNormal(bool fixGeometry=false); + + /** + * Maximizes the window. + */ + void showMaximized(); + + /** + * Returns if the window is currently maximized or not. + */ + bool isMaximized() const; + + /** + * Patched show to prevent the window from appearing in the taskbar. + */ + void show(); + + /** + * Returns true if the floating widget has a native titlebar or false if + * the floating widget has a QWidget based title bar + */ + bool hasNativeTitleBar(); +#endif + }; // class FloatingDockContainer } // namespace ads diff --git a/src/ads.qrc b/src/ads.qrc index 0c9f4e6..a29a542 100644 --- a/src/ads.qrc +++ b/src/ads.qrc @@ -10,5 +10,9 @@ images/tabs-menu-button.svg images/detach-button.svg images/detach-button-disabled.svg + images/maximize-button.svg + images/maximize-button-focused.svg + images/restore-button.svg + images/restore-button-focused.svg diff --git a/src/ads_globals.cpp b/src/ads_globals.cpp index b185e5e..322046e 100644 --- a/src/ads_globals.cpp +++ b/src/ads_globals.cpp @@ -38,12 +38,255 @@ #include "IconProvider.h" #include "ads_globals.h" +#ifdef Q_OS_LINUX +#include +#include +#include +#endif + + +#include namespace ads { namespace internal { +#ifdef Q_OS_LINUX +static QString _window_manager; +static QHash _xcb_atom_cache; + + +//============================================================================ +xcb_atom_t xcb_get_atom(const char *name) +{ + if (!QX11Info::isPlatformX11()) + { + return XCB_ATOM_NONE; + } + auto key = QString(name); + if(_xcb_atom_cache.contains(key)) + { + return _xcb_atom_cache[key]; + } + xcb_connection_t *connection = QX11Info::connection(); + xcb_intern_atom_cookie_t request = xcb_intern_atom(connection, 1, strlen(name), name); + xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(connection, request, NULL); + if (!reply) + { + return XCB_ATOM_NONE; + } + xcb_atom_t atom = reply->atom; + if(atom == XCB_ATOM_NONE) + { + ADS_PRINT("Unknown Atom response from XServer: " << name); + } + else + { + _xcb_atom_cache.insert(key, atom); + } + free(reply); + return atom; +} + + +//============================================================================ +void xcb_update_prop(bool set, WId window, const char *type, const char *prop, const char *prop2) +{ + auto connection = QX11Info::connection(); + xcb_atom_t type_atom = xcb_get_atom(type); + xcb_atom_t prop_atom = xcb_get_atom(prop); + xcb_client_message_event_t event; + event.response_type = XCB_CLIENT_MESSAGE; + event.format = 32; + event.sequence = 0; + event.window = window; + event.type = type_atom; + event.data.data32[0] = set ? 1 : 0; + event.data.data32[1] = prop_atom; + event.data.data32[2] = prop2 ? xcb_get_atom(prop2) : 0; + event.data.data32[3] = 0; + event.data.data32[4] = 0; + + xcb_send_event(connection, 0, window, + XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | XCB_EVENT_MASK_PROPERTY_CHANGE, + (const char *)&event); + xcb_flush(connection); +} + + +//============================================================================ +xcb_get_property_reply_t* _xcb_get_props(WId window, const char *type, unsigned int atom_type) +{ + if (!QX11Info::isPlatformX11()) + { + return nullptr; + } + xcb_connection_t *connection = QX11Info::connection(); + xcb_atom_t type_atom = xcb_get_atom(type); + if (type_atom == XCB_ATOM_NONE) + { + return nullptr; + } + xcb_get_property_cookie_t request = xcb_get_property_unchecked(connection, 0, window, type_atom, atom_type, 0, 1024); + xcb_get_property_reply_t *reply = xcb_get_property_reply(connection, request, nullptr); + if(reply && reply->type != atom_type) + { + ADS_PRINT("ATOM TYPE MISMATCH (" << type <<"). Expected: " << atom_type << " but got " << reply->type); + free(reply); + return nullptr; + } + return reply; +} + + +//============================================================================ +template +void xcb_get_prop_list(WId window, const char *type, QVector &ret, unsigned int atom_type) +{ + xcb_get_property_reply_t *reply = _xcb_get_props(window, type, atom_type); + if (reply && reply->format == 32 && reply->type == atom_type && reply->value_len > 0) + { + const xcb_atom_t *data = static_cast(xcb_get_property_value(reply)); + ret.resize(reply->value_len); + memcpy((void *)&ret.first(), (void *)data, reply->value_len * sizeof(T)); + } + free(reply); +} + + +//============================================================================ +QString xcb_get_prop_string(WId window, const char *type) +{ + QString ret; + // try utf8 first + xcb_atom_t utf_atom = xcb_get_atom("UTF8_STRING"); + if(utf_atom != XCB_ATOM_NONE) + { + xcb_get_property_reply_t *reply = _xcb_get_props(window, type, utf_atom); + if (reply && reply->format == 8 && reply->type == utf_atom) + { + const char *value = reinterpret_cast(xcb_get_property_value(reply)); + ret = QString::fromUtf8(value, xcb_get_property_value_length(reply)); + free(reply); + return ret; + } + free(reply); + } + // Fall back to XCB_ATOM_STRING + xcb_get_property_reply_t *reply = _xcb_get_props(window, type, XCB_ATOM_STRING); + if (reply && reply->format == 8 && reply->type == XCB_ATOM_STRING) + { + const char *value = reinterpret_cast(xcb_get_property_value(reply)); + ret = QString::fromLatin1(value, xcb_get_property_value_length(reply)); + } + free(reply); + return ret; +} + + +//============================================================================ +bool xcb_dump_props(WId window, const char *type) +{ + QVector atoms; + xcb_get_prop_list(window, type, atoms, XCB_ATOM_ATOM); + qDebug() << "\n\n!!!" << type << " - " << atoms.length(); + xcb_connection_t *connection = QX11Info::connection(); + for (auto atom : atoms) + { + auto foo = xcb_get_atom_name(connection, atom); + auto bar = xcb_get_atom_name_reply(connection, foo, nullptr); + qDebug() << "\t" << xcb_get_atom_name_name(bar); + free(bar); + } + return true; +} + + +//============================================================================ +void xcb_add_prop(bool state, WId window, const char *type, const char *prop) +{ + if (!QX11Info::isPlatformX11()) + { + return; + } + xcb_atom_t prop_atom = xcb_get_atom(prop); + xcb_atom_t type_atom = xcb_get_atom(type); + if (prop_atom == XCB_ATOM_NONE || type_atom == XCB_ATOM_NONE) + { + return; + } + QVector atoms; + xcb_get_prop_list(window, type, atoms, XCB_ATOM_ATOM); + int index = atoms.indexOf(prop_atom); + if (state && index == -1) + { + atoms.push_back(prop_atom); + } + else if (!state && index >= 0) + { + atoms.remove(index); + } + xcb_connection_t *connection = QX11Info::connection(); + xcb_change_property(connection, XCB_PROP_MODE_REPLACE, window, type_atom, XCB_ATOM_ATOM, 32, atoms.count(), atoms.constData()); + xcb_flush(connection); +} + + +//============================================================================ +QString detectWindowManagerX11() +{ + // Tries to detect the windowmanager via X11. + // See: https://specifications.freedesktop.org/wm-spec/1.3/ar01s03.html#idm46018259946000 + if (!QX11Info::isPlatformX11()) + { + return "UNKNOWN"; + } + xcb_connection_t *connection = QX11Info::connection(); + xcb_screen_t *first_screen = xcb_setup_roots_iterator (xcb_get_setup (connection)).data; + if(!first_screen) + { + ADS_PRINT("No screen found via XCB."); + return "UNKNOWN"; + } + // Get supporting window () + xcb_window_t root = first_screen->root; + xcb_window_t support_win = 0; + QVector sup_windows; + xcb_get_prop_list(root, "_NET_SUPPORTING_WM_CHECK", sup_windows, XCB_ATOM_WINDOW); + if(sup_windows.length() == 0) + { + // This doesn't seem to be in use anymore, but wmctrl does the same so lets play safe. + // Both XCB_ATOM_CARDINAL and XCB_ATOM_WINDOW break down to a uint32_t, so reusing sup_windows should be fine. + xcb_get_prop_list(root, "_WIN_SUPPORTING_WM_CHECK", sup_windows, XCB_ATOM_CARDINAL); + } + if(sup_windows.length() == 0) + { + ADS_PRINT("Failed to get the supporting window on non EWMH comform WM."); + return "UNKNOWN"; + } + support_win = sup_windows[0]; + QString ret = xcb_get_prop_string(support_win, "_NET_WM_NAME"); + if(ret.length() == 0) + { + ADS_PRINT("Empty WM name occured."); + return "UNKNOWN"; + } + return ret; +} + +//============================================================================ +QString windowManager() +{ + if(_window_manager.length() == 0) + { + _window_manager = detectWindowManagerX11(); + } + return _window_manager; +} +#endif + + //============================================================================ void replaceSplitterWidget(QSplitter* Splitter, QWidget* From, QWidget* To) { diff --git a/src/ads_globals.h b/src/ads_globals.h index 3523be5..c5fe28d 100644 --- a/src/ads_globals.h +++ b/src/ads_globals.h @@ -37,6 +37,10 @@ #include #include +#ifdef Q_OS_LINUX +#include +#endif + QT_FORWARD_DECLARE_CLASS(QAbstractButton) #ifndef ADS_STATIC @@ -122,6 +126,7 @@ enum eBitwiseOperator BitwiseOr }; + namespace internal { static const bool RestoreTesting = true; @@ -129,6 +134,33 @@ static const bool Restore = false; static const char* const ClosedProperty = "close"; static const char* const DirtyProperty = "dirty"; +#ifdef Q_OS_LINUX +// Utils to directly communicate with the X server +/** + * Get atom from cache or request it from the XServer. + */ +xcb_atom_t xcb_get_atom(const char *name); + +/** + * Add a property to a window. Only works on "hidden" windows. + */ +void xcb_add_prop(bool state, WId window, const char *type, const char *prop); +/** + * Updates up to two window properties. Can be set on a visible window. + */ +void xcb_update_prop(bool set, WId window, const char *type, const char *prop, const char *prop2 = nullptr); +/** + * Only for debugging purposes. + */ +bool xcb_dump_props(WId window, const char *type); +/** + * Gets the active window manager from the X11 Server. + * Requires a EWMH conform window manager (Allmost all common used ones are). + * Returns "UNKNOWN" otherwise. + */ +QString windowManager(); +#endif + /** * Replace the from widget in the given splitter with the To widget */ diff --git a/src/images/maximize-button-focused.svg b/src/images/maximize-button-focused.svg new file mode 100644 index 0000000..cdbb56d --- /dev/null +++ b/src/images/maximize-button-focused.svg @@ -0,0 +1,145 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Jemis Mali + + + + + image/svg+xml + + + + + + diff --git a/src/images/maximize-button.svg b/src/images/maximize-button.svg new file mode 100644 index 0000000..4084eb8 --- /dev/null +++ b/src/images/maximize-button.svg @@ -0,0 +1,145 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Jemis Mali + + + + + image/svg+xml + + + + + + diff --git a/src/images/restore-button-focused.svg b/src/images/restore-button-focused.svg new file mode 100644 index 0000000..45b32e9 --- /dev/null +++ b/src/images/restore-button-focused.svg @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Jemis Mali + + + + + image/svg+xml + + + + + + diff --git a/src/images/restore-button.svg b/src/images/restore-button.svg new file mode 100644 index 0000000..46175d4 --- /dev/null +++ b/src/images/restore-button.svg @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Jemis Mali + + + + + image/svg+xml + + + + + + diff --git a/src/linux/FloatingWidgetTitleBar.cpp b/src/linux/FloatingWidgetTitleBar.cpp index e8e077b..8546748 100644 --- a/src/linux/FloatingWidgetTitleBar.cpp +++ b/src/linux/FloatingWidgetTitleBar.cpp @@ -46,6 +46,7 @@ namespace ads using tTabLabel = CElidingLabel; using tCloseButton = QToolButton; +using tMaximizeButton = QToolButton; /** * @brief Private data class of public interface CFloatingWidgetTitleBar @@ -56,8 +57,12 @@ struct FloatingWidgetTitleBarPrivate QLabel *IconLabel = nullptr; tTabLabel *TitleLabel; tCloseButton *CloseButton = nullptr; + tMaximizeButton* MaximizeButton = nullptr; CFloatingDockContainer *FloatingWidget = nullptr; eDragState DragState = DraggingInactive; + QIcon MaximizeIcon; + QIcon NormalIcon; + bool Maximized = false; FloatingWidgetTitleBarPrivate(CFloatingWidgetTitleBar *_public) : _this(_public) @@ -83,6 +88,10 @@ void FloatingWidgetTitleBarPrivate::createLayout() CloseButton->setObjectName("floatingTitleCloseButton"); CloseButton->setAutoRaise(true); + MaximizeButton = new tMaximizeButton(); + MaximizeButton->setObjectName("floatingTitleMaximizeButton"); + MaximizeButton->setAutoRaise(true); + // The standard icons do does not look good on high DPI screens QIcon CloseIcon; QPixmap normalPixmap = _this->style()->standardPixmap( @@ -97,6 +106,12 @@ void FloatingWidgetTitleBarPrivate::createLayout() CloseButton->setFocusPolicy(Qt::NoFocus); _this->connect(CloseButton, SIGNAL(clicked()), SIGNAL(closeRequested())); + _this->setMaximizedIcon(false); + MaximizeButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + MaximizeButton->setVisible(true); + MaximizeButton->setFocusPolicy(Qt::NoFocus); + _this->connect(MaximizeButton, &QPushButton::clicked, _this, &CFloatingWidgetTitleBar::maximizeRequested); + QFontMetrics fm(TitleLabel->font()); int Spacing = qRound(fm.height() / 4.0); @@ -107,6 +122,7 @@ void FloatingWidgetTitleBarPrivate::createLayout() _this->setLayout(Layout); Layout->addWidget(TitleLabel, 1); Layout->addSpacing(Spacing); + Layout->addWidget(MaximizeButton); Layout->addWidget(CloseButton); Layout->setAlignment(Qt::AlignCenter); @@ -120,6 +136,15 @@ CFloatingWidgetTitleBar::CFloatingWidgetTitleBar(CFloatingDockContainer *parent) { d->FloatingWidget = parent; d->createLayout(); + + auto normalPixmap = this->style()->standardPixmap(QStyle::SP_TitleBarNormalButton, 0, d->MaximizeButton); + d->NormalIcon.addPixmap(normalPixmap, QIcon::Normal); + d->NormalIcon.addPixmap(internal::createTransparentPixmap(normalPixmap, 0.25), QIcon::Disabled); + + auto maxPixmap = this->style()->standardPixmap(QStyle::SP_TitleBarMaxButton, 0, d->MaximizeButton); + d->MaximizeIcon.addPixmap(maxPixmap, QIcon::Normal); + d->MaximizeIcon.addPixmap(internal::createTransparentPixmap(maxPixmap, 0.25), QIcon::Disabled); + setMaximizedIcon(d->Maximized); } //============================================================================ @@ -141,17 +166,19 @@ void CFloatingWidgetTitleBar::mousePressEvent(QMouseEvent *ev) Super::mousePressEvent(ev); } + //============================================================================ void CFloatingWidgetTitleBar::mouseReleaseEvent(QMouseEvent *ev) { d->DragState = DraggingInactive; if (d->FloatingWidget) { - d->FloatingWidget->finishDragging(); + d->FloatingWidget->finishDragging(); } Super::mouseReleaseEvent(ev); } + //============================================================================ void CFloatingWidgetTitleBar::mouseMoveEvent(QMouseEvent *ev) { @@ -165,6 +192,10 @@ void CFloatingWidgetTitleBar::mouseMoveEvent(QMouseEvent *ev) // move floating window if (DraggingFloatingWidget == d->DragState) { + if(d->FloatingWidget->isMaximized()) + { + d->FloatingWidget->showNormal(true); + } d->FloatingWidget->moveFloating(); Super::mouseMoveEvent(ev); return; @@ -186,11 +217,77 @@ void CFloatingWidgetTitleBar::setTitle(const QString &Text) d->TitleLabel->setText(Text); } - //============================================================================ void CFloatingWidgetTitleBar::updateStyle() { internal::repolishStyle(this, internal::RepolishDirectChildren); } + +//============================================================================ +void CFloatingWidgetTitleBar::mouseDoubleClickEvent(QMouseEvent *event) +{ + if (event->buttons() & Qt::LeftButton) + { + emit maximizeRequested(); + event->accept(); + } + else + { + QWidget::mouseDoubleClickEvent(event); + } +} + + +//============================================================================ +void CFloatingWidgetTitleBar::setMaximizedIcon(bool maximized) +{ + d->Maximized = maximized; + if (maximized) + { + d->MaximizeButton->setIcon(d->NormalIcon); + } + else + { + d->MaximizeButton->setIcon(d->MaximizeIcon); + } +} + + +//============================================================================ +void CFloatingWidgetTitleBar::setMaximizeIcon(const QIcon& Icon) +{ + d->MaximizeIcon = Icon; + if (d->Maximized) + { + setMaximizedIcon(d->Maximized); + } +} + + +//============================================================================ +void CFloatingWidgetTitleBar::setNormalIcon(const QIcon& Icon) +{ + d->NormalIcon = Icon; + if (!d->Maximized) + { + setMaximizedIcon(d->Maximized); + } +} + + +//============================================================================ +QIcon CFloatingWidgetTitleBar::maximizeIcon() const +{ + return d->MaximizeIcon; +} + + +//============================================================================ +QIcon CFloatingWidgetTitleBar::normalIcon() const +{ + return d->NormalIcon; +} + + } // namespace ads diff --git a/src/linux/FloatingWidgetTitleBar.h b/src/linux/FloatingWidgetTitleBar.h index 13914db..ad6904f 100644 --- a/src/linux/FloatingWidgetTitleBar.h +++ b/src/linux/FloatingWidgetTitleBar.h @@ -30,6 +30,7 @@ // INCLUDES //============================================================================ #include +#include namespace ads { @@ -48,6 +49,8 @@ struct FloatingWidgetTitleBarPrivate; class CFloatingWidgetTitleBar : public QFrame { Q_OBJECT + Q_PROPERTY(QIcon maximizeIcon READ maximizeIcon WRITE setMaximizeIcon) + Q_PROPERTY(QIcon normalIcon READ normalIcon WRITE setNormalIcon) private: FloatingWidgetTitleBarPrivate *d; ///< private data (pimpl) @@ -55,6 +58,12 @@ protected: virtual void mousePressEvent(QMouseEvent *ev) override; virtual void mouseReleaseEvent(QMouseEvent *ev) override; virtual void mouseMoveEvent(QMouseEvent *ev) override; + virtual void mouseDoubleClickEvent(QMouseEvent *event) override; + + void setMaximizeIcon(const QIcon& Icon); + QIcon maximizeIcon() const; + void setNormalIcon(const QIcon& Icon); + QIcon normalIcon() const; public: using Super = QWidget; @@ -80,11 +89,21 @@ public: */ void updateStyle(); + /** + * Change the maximize button icon according to current windows state + */ + void setMaximizedIcon(bool maximized); + signals: /** * This signal is emitted, if the close button is clicked. */ void closeRequested(); + + /** + * This signal is emitted, if the maximize button is clicked. + */ + void maximizeRequested(); }; } // namespace ads #endif // FLOATINGWIDGETTITLEBAR_H diff --git a/src/src.pro b/src/src.pro index fba3562..9cd81f4 100644 --- a/src/src.pro +++ b/src/src.pro @@ -72,6 +72,7 @@ SOURCES += \ unix { HEADERS += linux/FloatingWidgetTitleBar.h SOURCES += linux/FloatingWidgetTitleBar.cpp +QT += x11extras } isEmpty(PREFIX){ diff --git a/src/stylesheets/default_linux.css b/src/stylesheets/default_linux.css index 56b950e..51e52ac 100644 --- a/src/stylesheets/default_linux.css +++ b/src/stylesheets/default_linux.css @@ -92,13 +92,25 @@ QScrollArea#dockWidgetScrollArea { qproperty-iconSize: 16px; } -#floatingTitleCloseButton { - qproperty-icon: url(:/ads/images/close-button.svg); + +ads--CFloatingWidgetTitleBar { + background: palette(midlight); + qproperty-maximizeIcon: url(:/ads/images/maximize-button.svg); + qproperty-normalIcon: url(:/ads/images/restore-button.svg); +} + + +#floatingTitleCloseButton, #floatingTitleMaximizeButton { qproperty-iconSize: 16px; border: none; margin: 3px; } + +#floatingTitleCloseButton { + qproperty-icon: url(:/ads/images/close-button.svg); +} + #floatingTitleCloseButton:hover { background: rgba(0, 0, 0, 24); border: none; diff --git a/src/stylesheets/focus_highlighting_linux.css b/src/stylesheets/focus_highlighting_linux.css index 3f1b393..bea52d0 100644 --- a/src/stylesheets/focus_highlighting_linux.css +++ b/src/stylesheets/focus_highlighting_linux.css @@ -2,89 +2,89 @@ * Default style sheet on Linux Platforms with focus highlighting flag enabled */ ads--CDockContainerWidget { - background: palette(dark); + background: palette(dark); } ads--CDockContainerWidget QSplitter::handle { - background: palette(dark); + background: palette(dark); } ads--CDockAreaWidget { - background: palette(window); - border: 1px solid white; + background: palette(window); + border: 1px solid white; } ads--CDockAreaWidget #tabsMenuButton::menu-indicator { - image: none; + image: none; } ads--CDockWidgetTab { - background: palette(window); - border-color: palette(light); - border-style: solid; - border-width: 0 1px 0 0; - padding: 0 0px; + background: palette(window); + border-color: palette(light); + border-style: solid; + border-width: 0 1px 0 0; + padding: 0 0px; } ads--CDockWidgetTab[activeTab="true"] { - background: qlineargradient(spread : pad, x1 : 0, y1 : 0, x2 : 0, y2 : 0.5, stop : 0 - palette(window), stop:1 palette(light)); - /*background: palette(highlight);*/ + background: qlineargradient(spread : pad, x1 : 0, y1 : 0, x2 : 0, y2 : 0.5, stop : 0 + palette(window), stop:1 palette(light)); + /*background: palette(highlight);*/ } ads--CDockWidgetTab QLabel { - color: palette(dark); + color: palette(dark); } ads--CDockWidgetTab[activeTab="true"] QLabel { - color: palette(foreground); + color: palette(foreground); } ads--CDockWidget { - background: palette(light); - border-color: palette(light); - border-style: solid; - border-width: 1px 0 0 0; + background: palette(light); + border-color: palette(light); + border-style: solid; + border-width: 1px 0 0 0; } ads--CTitleBarButton { - padding: 0px 0px; + padding: 0px 0px; } QScrollArea#dockWidgetScrollArea { - padding: 0px; - border: none; + padding: 0px; + border: none; } #dockAreaCloseButton { - qproperty-icon: url(:/ads/images/close-button.svg), - url(:/ads/images/close-button-disabled.svg) disabled; - qproperty-iconSize: 16px; + qproperty-icon: url(:/ads/images/close-button.svg), + url(:/ads/images/close-button-disabled.svg) disabled; + qproperty-iconSize: 16px; } #detachGroupButton { - qproperty-icon: url(:/ads/images/detach-button.svg), - url(:/ads/images/detach-button-disabled.svg) disabled; - qproperty-iconSize: 16px; + qproperty-icon: url(:/ads/images/detach-button.svg), + url(:/ads/images/detach-button-disabled.svg) disabled; + qproperty-iconSize: 16px; } #tabCloseButton { - margin-top: 2px; - background: none; - border: none; - padding: 0px -2px; - qproperty-icon: url(:/ads/images/close-button.svg), - url(:/ads/images/close-button-disabled.svg) disabled; - qproperty-iconSize: 16px; + margin-top: 2px; + background: none; + border: none; + padding: 0px -2px; + qproperty-icon: url(:/ads/images/close-button.svg), + url(:/ads/images/close-button-disabled.svg) disabled; + qproperty-iconSize: 16px; } #tabCloseButton:hover { - /*border: 1px solid rgba(0, 0, 0, 32);*/ - background: rgba(0, 0, 0, 24); + /*border: 1px solid rgba(0, 0, 0, 32);*/ + background: rgba(0, 0, 0, 24); } #tabCloseButton:pressed { - background: rgba(0, 0, 0, 48); + background: rgba(0, 0, 0, 48); } @@ -96,75 +96,99 @@ QScrollArea#dockWidgetScrollArea { /* Focus related styling */ ads--CDockWidgetTab[focused="true"] { - background: palette(highlight); - border-color: palette(highlight); + background: palette(highlight); + border-color: palette(highlight); } ads--CDockWidgetTab[focused="true"]>#tabCloseButton { - qproperty-icon: url(:/ads/images/close-button-focused.svg) + qproperty-icon: url(:/ads/images/close-button-focused.svg) } ads--CDockWidgetTab[focused="true"]>#tabCloseButton:hover { - background: rgba(255, 255, 255, 48); + background: rgba(255, 255, 255, 48); } ads--CDockWidgetTab[focused="true"]>#tabCloseButton:pressed { - background: rgba(255, 255, 255, 92); + background: rgba(255, 255, 255, 92); } ads--CDockWidgetTab[focused="true"] QLabel { - color: palette(light); + color: palette(light); } ads--CDockAreaTitleBar { - background: transparent; - border-bottom: 2px solid palette(light); - padding-bottom: 0px; + background: transparent; + border-bottom: 2px solid palette(light); + padding-bottom: 0px; } ads--CDockAreaWidget[focused="true"] ads--CDockAreaTitleBar { - background: transparent; - border-bottom: 2px solid palette(highlight); - padding-bottom: 0px; + background: transparent; + border-bottom: 2px solid palette(highlight); + padding-bottom: 0px; } +ads--CFloatingWidgetTitleBar { + qproperty-maximizeIcon: url(:/ads/images/maximize-button.svg); + qproperty-normalIcon: url(:/ads/images/restore-button.svg); +} + +/* does not properly work on KDE ads--CFloatingDockContainer[isActiveWindow="true"] ads--CFloatingWidgetTitleBar { - background: palette(highlight); + background: palette(highlight); + qproperty-maximizeIcon: url(:/ads/images/maximize-button-focused.svg); + qproperty-normalIcon: url(:/ads/images/restore-button-focused.svg); } ads--CFloatingDockContainer[isActiveWindow="true"] #floatingTitleLabel { - color: palette(light); + color: palette(light); } +*/ + +#floatingTitleCloseButton, #floatingTitleMaximizeButton { + qproperty-iconSize: 16px; + border: none; + margin: 6px 3px 6px 3px; +} + #floatingTitleCloseButton { qproperty-icon: url(:/ads/images/close-button.svg); - qproperty-iconSize: 16px; - border: none; - margin: 3px; } -#floatingTitleCloseButton:hover { + +#floatingTitleCloseButton:hover, #floatingTitleMaximizeButton:hover { background: rgba(0, 0, 0, 24); border: none; } -#floatingTitleCloseButton:pressed { + +#floatingTitleCloseButton:pressed, #floatingTitleMaximizeButton:pressed { background: rgba(0, 0, 0, 48); } +ads--CFloatingDockContainer[isActiveWindow="true"] #floatingTitleMaximizeButton { + qproperty-iconSize: 16px; +} + + +/* does not properly work on KDE ads--CFloatingDockContainer[isActiveWindow="true"] #floatingTitleCloseButton { qproperty-icon: url(:/ads/images/close-button-focused.svg); qproperty-iconSize: 16px; } -ads--CFloatingDockContainer[isActiveWindow="true"] #floatingTitleCloseButton:hover { +ads--CFloatingDockContainer[isActiveWindow="true"] #floatingTitleCloseButton:hover, +ads--CFloatingDockContainer[isActiveWindow="true"] #floatingTitleMaximizeButton:hover { background: rgba(255, 255, 255, 48); } -ads--CFloatingDockContainer[isActiveWindow="true"] #floatingTitleCloseButton:pressed { +ads--CFloatingDockContainer[isActiveWindow="true"] #floatingTitleCloseButton:pressed, +ads--CFloatingDockContainer[isActiveWindow="true"] #floatingTitleMaximizeButton:pressed { background: rgba(255, 255, 255, 92); } +*/