diff --git a/.travis.yml b/.travis.yml index 7e38344..d1cf9e6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,7 @@ matrix: dist: trusty group: stable before_install: - - sudo apt-get install libqt5x11extras5-dev + - sudo apt-get -y install libqt5x11extras5-dev addons: apt: sources: @@ -21,6 +21,7 @@ matrix: - qt55base - qt55tools - qt55x11extras + - libqt5x11extras5-dev - gcc-9 - g++-9 script: @@ -39,7 +40,7 @@ matrix: - xvfb compiler: gcc before_install: - - sudo apt-get install libqt5x11extras5-dev + - sudo apt-get -y install libqt5x11extras5-dev addons: apt: sources: @@ -50,6 +51,7 @@ matrix: - qt514base - qt514tools - qt514x11extras + - libqt5x11extras5-dev - gcc-9 - g++-9 - libc6-i386 @@ -71,9 +73,9 @@ matrix: services: - xvfb compiler: gcc - addons: before_install: - - sudo apt-get install libqt5x11extras5-dev + - sudo apt-get -y install libqt5x11extras5-dev + addons: apt: sources: - ubuntu-toolchain-r-test @@ -83,6 +85,7 @@ matrix: - qt514base - qt514tools - qt514x11extras + - libqt5x11extras5-dev - gcc-9 - g++-9 - libc6-i386 @@ -105,7 +108,7 @@ matrix: - xvfb compiler: gcc before_install: - - sudo apt-get install libqt5x11extras5-dev + - sudo apt-get -y install libqt5x11extras5-dev addons: apt: sources: @@ -116,6 +119,7 @@ matrix: - qt514base - qt514tools - qt514x11extras + - libqt5x11extras5-dev - gcc-9 - g++-9 - libc6-i386 @@ -144,7 +148,7 @@ matrix: - xvfb compiler: gcc before_install: - - sudo apt-get install libqt5x11extras5-dev + - sudo apt-get -y install libqt5x11extras5-dev addons: apt: sources: @@ -155,6 +159,7 @@ matrix: - qt514base - qt514tools - qt514x11extras + - libqt5x11extras5-dev - gcc-9 - g++-9 - libc6-i386 diff --git a/README.md b/README.md index c23768e..86ab5a0 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,20 @@ integrated development environments (IDEs) such as Visual Studio. ## New and Noteworthy +The [release 3.6.0](https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/releases/tag/3.6.0) +adds some nice new features: + +- support for [central widget](doc/user-guide.md#central-widget) concept + +![Central Widget](doc/central_widget.gif) + +- support for [native floating widgets](doc/user-guide.md#floatingcontainerforcenativetitlebar-linux-only) on Linux + +![FloatingContainerForceNativeTitleBar true](doc/cfg_flag_FloatingContainerForceNativeTitleBar_true.png) + +Both features are contributions from ADS users. Read the [documentation](doc/user-guide.md) +to learn more about both new features. + The [release 3.5.0](https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/releases/tag/3.5.0) adds the new [focus highlighting](doc/user-guide.md#focushighlighting) feature. This optional feature enables highlighting of the focused dock widget like you diff --git a/demo/MainWindow.cpp b/demo/MainWindow.cpp index 5127242..47add8f 100644 --- a/demo/MainWindow.cpp +++ b/demo/MainWindow.cpp @@ -109,7 +109,7 @@ static void appendFeaturStringToWindowTitle(ads::CDockWidget* DockWidget) static QIcon svgIcon(const QString& File) { // This is a workaround, because in item views SVG icons are not - // properly scaled an look blurry or pixelate + // properly scaled and look blurry or pixelate QIcon SvgIcon(File); SvgIcon.addPixmap(SvgIcon.pixmap(92)); return SvgIcon; @@ -166,6 +166,7 @@ struct MainWindowPrivate QComboBox* PerspectiveComboBox = nullptr; ads::CDockManager* DockManager = nullptr; ads::CDockWidget* WindowTitleTestDockWidget = nullptr; + ads::CDockWidget* LastDockedEditor = nullptr; MainWindowPrivate(CMainWindow* _public) : _this(_public) {} @@ -720,7 +721,17 @@ void CMainWindow::createEditor() } else { - d->DockManager->addDockWidget(ads::TopDockWidgetArea, DockWidget); + ads::CDockAreaWidget* EditorArea = d->LastDockedEditor ? d->LastDockedEditor->dockAreaWidget() : nullptr; + if (EditorArea) + { + d->DockManager->setConfigFlag(ads::CDockManager::EqualSplitOnInsertion, true); + d->DockManager->addDockWidget(ads::RightDockWidgetArea, DockWidget, EditorArea); + } + else + { + d->DockManager->addDockWidget(ads::TopDockWidgetArea, DockWidget); + } + d->LastDockedEditor = DockWidget; } } diff --git a/demo/demo.py b/demo/demo.py index ff268f6..5ba8d42 100644 --- a/demo/demo.py +++ b/demo/demo.py @@ -65,7 +65,7 @@ def append_feature_string_to_window_title(dock_widget: QtAds.CDockWidget): def svg_icon(filename: str): '''Helper function to create an SVG icon''' # This is a workaround, because because in item views SVG icons are not - # properly scaled an look blurry or pixelate + # properly scaled and look blurry or pixelate icon = QIcon(filename) icon.addPixmap(icon.pixmap(92)) return icon @@ -104,6 +104,8 @@ class MainWindow(MainWindowUI, MainWindowBase): self.perspective_list_action = None self.perspective_combo_box = None self.dock_manager = None + self.window_title_test_dock_widget = None + self.last_docked_editor = None self.setupUi(self) self.create_actions() @@ -184,6 +186,7 @@ class MainWindow(MainWindowUI, MainWindowBase): # special_dock_area.setAllowedAreas(QtAds.LeftDockWidgetArea | QtAds.RightDockWidgetArea) # just for testing dock_widget = self.create_long_text_label_dock_widget() + self.window_title_test_dock_widget = dock_widget dock_widget.setFeature(QtAds.CDockWidget.DockWidgetFocusable, False) self.dock_manager.addDockWidget(QtAds.LeftDockWidgetArea, dock_widget) file_system_widget = self.create_file_system_tree_dock_widget() @@ -203,8 +206,8 @@ class MainWindow(MainWindowUI, MainWindowBase): 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) + # HideSingleWidgetTitleBar functionality + # top_dock_area.setDockAreaFlag(QtAds.CDockAreaWidget.HideSingleWidgetTitleBar, True) QtAds.CDockComponentsFactory.resetDefaultFactory() # We create a calendar widget and clear all flags to prevent the dock area @@ -354,7 +357,6 @@ class MainWindow(MainWindowUI, MainWindowBase): def create_editor(self): sender = self.sender() floating = sender.property("Floating") - print("Floating:", floating) dock_widget = self.create_editor_widget() dock_widget.setFeature(QtAds.CDockWidget.DockWidgetDeleteOnClose, True) dock_widget.closeRequested.connect(self.on_editor_close_requested) @@ -363,7 +365,13 @@ class MainWindow(MainWindowUI, MainWindowBase): floating_widget = self.dock_manager.addDockWidgetFloating(dock_widget) floating_widget.move(QPoint(20, 20)) else: - self.dock_manager.addDockWidget(QtAds.TopDockWidgetArea, dock_widget) + editor_area = self.last_docked_editor.dockAreaWidget() if self.last_docked_editor is not None else None + if editor_area is not None: + self.dock_manager.setConfigFlag(QtAds.CDockManager.EqualSplitOnInsertion, True) + self.dock_manager.addDockWidget(QtAds.RightDockWidgetArea, dock_widget, editor_area) + else: + self.dock_manager.addDockWidget(QtAds.TopDockWidgetArea, dock_widget) + self.last_docked_editor = dock_widget def on_editor_close_requested(self): dock_widget = self.sender() @@ -382,6 +390,16 @@ class MainWindow(MainWindowUI, MainWindowBase): dialog = CStatusDialog(self.dock_manager) dialog.exec_() + + def toggle_dock_widget_window_title(self): + title = self.window_title_test_dock_widget.windowTitle() + i = title.find(" (Test) ") + if i == -1: + title += " (Test) " + else: + title = title[i] + self.window_title_test_dock_widget.setWindowTitle(title) + def save_state(self): ''' Saves the dock manager state and the main window geometry diff --git a/doc/central_widget.gif b/doc/central_widget.gif new file mode 100644 index 0000000..e766741 Binary files /dev/null and b/doc/central_widget.gif differ diff --git a/doc/cfg_flag_EqualSplitOnInsertion_false.png b/doc/cfg_flag_EqualSplitOnInsertion_false.png new file mode 100644 index 0000000..a72cb77 Binary files /dev/null and b/doc/cfg_flag_EqualSplitOnInsertion_false.png differ diff --git a/doc/cfg_flag_EqualSplitOnInsertion_true.png b/doc/cfg_flag_EqualSplitOnInsertion_true.png new file mode 100644 index 0000000..69891ac Binary files /dev/null and b/doc/cfg_flag_EqualSplitOnInsertion_true.png differ diff --git a/doc/cfg_flag_FloatingContainerForceNativeTitleBar_false.png b/doc/cfg_flag_FloatingContainerForceNativeTitleBar_false.png new file mode 100644 index 0000000..3cdb8c9 Binary files /dev/null and b/doc/cfg_flag_FloatingContainerForceNativeTitleBar_false.png differ diff --git a/doc/cfg_flag_FloatingContainerForceNativeTitleBar_true.png b/doc/cfg_flag_FloatingContainerForceNativeTitleBar_true.png new file mode 100644 index 0000000..2b06fb2 Binary files /dev/null and b/doc/cfg_flag_FloatingContainerForceNativeTitleBar_true.png differ diff --git a/doc/user-guide.md b/doc/user-guide.md index bad8c0c..45a6da2 100644 --- a/doc/user-guide.md +++ b/doc/user-guide.md @@ -24,6 +24,10 @@ - [`FloatingContainerHasWidgetIcon`](#floatingcontainerhaswidgeticon) - [`HideSingleCentralWidgetTitleBar`](#hidesinglecentralwidgettitlebar) - [`FocusHighlighting`](#focushighlighting) + - [`EqualSplitOnInsertion`](#equalsplitoninsertion) + - [`FloatingContainerForceNativeTitleBar` (Linux only)](#floatingcontainerforcenativetitlebar-linux-only) + - [`FloatingContainerForceQWidgetTitleBar` (Linux only)](#floatingcontainerforceqwidgettitlebar-linux-only) +- [Central Widget](#central-widget) - [Styling](#styling) - [Disabling the Internal Style Sheet](#disabling-the-internal-style-sheet) @@ -409,6 +413,93 @@ bool CMainWindow::eventFilter(QObject *watched, QEvent *event) } ``` +### `EqualSplitOnInsertion` + +This flag configures how the space is distributed if a new dock widget is +inserted into an existing dock area. The flag is disabled by default. If 3 +dock widgets are inserted with the following code + +```c++ +d->DockManager->addDockWidget(ads::RightDockWidgetArea, DockWidget, EditorArea); +``` + +then this is the result, if the flag is disabled: + +![EqualSplitOnInsertion false](cfg_flag_EqualSplitOnInsertion_false.png) + +If the flag is enabled, then the space is equally distributed to all widgets +in a splitter: + +![EqualSplitOnInsertion true](cfg_flag_EqualSplitOnInsertion_true.png) + + +### `FloatingContainerForceNativeTitleBar` (Linux only) + +Since release 3.6 the library supports native title bars and window decorations +for floating widgets on Linux (thanks to a user contribution). +Native title bars and window decorations are supported by most Linux window +managers, such as Compiz or Xfwm. Some window managers like KWin do not properly +support this feature. Native floating widgets look better because of the native +styling and the support all window manager features like snapping to window +borders or maximizing. The library tries to detect the window manager during +runtime and activates native window decorations if possible: + +![FloatingContainerForceNativeTitleBar true](cfg_flag_FloatingContainerForceNativeTitleBar_true.png) + +If you would like to overwrite this autodetection, then you can activate this +flag to force native window titlebars. You can overwrite autodetection and this +flag, if you set the environment variable `ADS_UseNativeTitle` to 0 or 1. + +### `FloatingContainerForceQWidgetTitleBar` (Linux only) + +If your window manager (i.e. KWin) does not properly support native floating +windows, the docking library falls back to QWidget based floating widget +title bars. + +![FloatingContainerForceNativeTitleBar false](cfg_flag_FloatingContainerForceNativeTitleBar_false.png) + +If you would like to overwrite autodetection, then you can activate this flag +to force QWidget based title bars. You can overwrite autodetection and this +flag, if you set the environment variable `ADS_UseNativeTitle` to 0 or 1. + +## Central Widget + +The Advanced Docking System has been developed to overcome the limitations of +the native Qt docking system with its central widget concept. This was the +reason that until version 3.6 of the library, there was no support for such +thing like a central widget. Thanks to the contribution of a user the library +now supports a central widget. + +In the Advanced Docking System a central widget is a docking widget that is +neither closable nor movable or floatable. A central widget has no title bar +and so it is not possible for the user to hide, close or drag the central +widget. If there is a central widget, then also the distribution of the sizes +for the dock widgets around the central widget is different: + +- **no central widget (default)** - on resizing the available space is +distributed to all dock widgets - the size of all dock widgets +shrinks or grows +- **with central widget** - on resizing only the central widget is resized - the +dock widgets around the central widget keep their size (see the animation below) + +![Central Widget](central_widget.gif) + +To set a central widget, you just need to pass your central dock widget +to the dock manager `setCentralWidget` function: + +```c++ +auto* CentralDockArea = DockManager->setCentralWidget(CentralDockWidget); +``` + +See the `centralwidget` example to learn how it works. + +> ##### Note +> The central widget needs to be the first dock widget that is added to the +> dock manager. The function does not work and returns a `nullptr` if there +> are already other dock widgets registered. So `setCentralWidget` should be +> the first function that you call when adding dock widgets. + + ## Styling The Advanced Docking System supports styling via [Qt Style Sheets](https://doc.qt.io/qt-5/stylesheet.html). All components like splitters, tabs, buttons, titlebar and diff --git a/examples/centralwidget/centralWidget.py b/examples/centralwidget/centralWidget.py new file mode 100644 index 0000000..e3f31c6 --- /dev/null +++ b/examples/centralwidget/centralWidget.py @@ -0,0 +1,115 @@ +import os +import sys + +from PyQt5 import uic +from PyQt5.QtCore import Qt, QTimer, QDir, QSignalBlocker +from PyQt5.QtGui import QCloseEvent, QIcon +from PyQt5.QtWidgets import (QApplication, QLabel, QCalendarWidget, QFrame, QTreeView, + QTableWidget, QFileSystemModel, QPlainTextEdit, QToolBar, + QWidgetAction, QComboBox, QAction, QSizePolicy, QInputDialog) + +from PyQtAds import QtAds + +UI_FILE = os.path.join(os.path.dirname(__file__), 'mainwindow.ui') +MainWindowUI, MainWindowBase = uic.loadUiType(UI_FILE) + +import demo_rc # pyrcc5 demo\demo.qrc -o examples\centralWidget\demo_rc.py + + +def svg_icon(filename: str): + '''Helper function to create an SVG icon''' + # This is a workaround, because because in item views SVG icons are not + # properly scaled and look blurry or pixelate + icon = QIcon(filename) + icon.addPixmap(icon.pixmap(92)) + return icon + + +class MainWindow(MainWindowUI, MainWindowBase): + + def __init__(self, parent=None): + super().__init__(parent) + + self.setupUi(self) + + QtAds.CDockManager.setConfigFlag(QtAds.CDockManager.OpaqueSplitterResize, True) + QtAds.CDockManager.setConfigFlag(QtAds.CDockManager.XmlCompressionEnabled, False) + QtAds.CDockManager.setConfigFlag(QtAds.CDockManager.FocusHighlighting, True) + self.dock_manager = QtAds.CDockManager(self) + + # Set central widget + text_edit = QPlainTextEdit() + text_edit.setPlaceholderText("This is the central editor. Enter your text here.") + central_dock_widget = QtAds.CDockWidget("CentralWidget") + central_dock_widget.setWidget(text_edit) + central_dock_area = self.dock_manager.setCentralWidget(central_dock_widget) + central_dock_area.setAllowedAreas(QtAds.DockWidgetArea.OuterDockAreas) + + # create other dock widgets + file_tree = QTreeView() + file_tree.setFrameShape(QFrame.NoFrame) + file_model = QFileSystemModel(file_tree) + file_model.setRootPath(QDir.currentPath()) + file_tree.setModel(file_model) + data_dock_widget = QtAds.CDockWidget("File system") + data_dock_widget.setWidget(file_tree) + data_dock_widget.resize(150, 250) + data_dock_widget.setMinimumSize(100, 250) + file_area = self.dock_manager.addDockWidget(QtAds.DockWidgetArea.LeftDockWidgetArea, data_dock_widget, central_dock_area) + self.menuView.addAction(data_dock_widget.toggleViewAction()) + + table = QTableWidget() + table.setColumnCount(3) + table.setRowCount(10) + table_dock_widget = QtAds.CDockWidget("Table") + table_dock_widget.setWidget(table) + table_dock_widget.setMinimumSizeHintMode(QtAds.CDockWidget.MinimumSizeHintFromDockWidget) + table_dock_widget.resize(250, 150) + table_dock_widget.setMinimumSize(200, 150) + self.dock_manager.addDockWidget(QtAds.DockWidgetArea.BottomDockWidgetArea, table_dock_widget, file_area) + self.menuView.addAction(table_dock_widget.toggleViewAction()) + + properties_table = QTableWidget() + properties_table.setColumnCount(3) + properties_table.setRowCount(10) + properties_dock_widget = QtAds.CDockWidget("Properties") + properties_dock_widget.setWidget(properties_table) + properties_dock_widget.setMinimumSizeHintMode(QtAds.CDockWidget.MinimumSizeHintFromDockWidget) + properties_dock_widget.resize(250, 150) + properties_dock_widget.setMinimumSize(200,150) + self.dock_manager.addDockWidget(QtAds.DockWidgetArea.RightDockWidgetArea, properties_dock_widget, central_dock_area) + self.menuView.addAction(properties_dock_widget.toggleViewAction()) + + self.create_perspective_ui() + + def create_perspective_ui(self): + save_perspective_action = QAction("Create Perspective", self) + save_perspective_action.setIcon(svg_icon(":/adsdemo/images/picture_in_picture.svg")) + save_perspective_action.triggered.connect(self.save_perspective) + perspective_list_action = QWidgetAction(self) + self.perspective_combobox = QComboBox(self) + self.perspective_combobox.setSizeAdjustPolicy(QComboBox.AdjustToContents) + self.perspective_combobox.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) + self.perspective_combobox.activated[str].connect(self.dock_manager.openPerspective) + perspective_list_action.setDefaultWidget(self.perspective_combobox) + self.toolBar.addSeparator() + self.toolBar.addAction(perspective_list_action) + self.toolBar.addAction(save_perspective_action) + + def save_perspective(self): + perspective_name, ok = QInputDialog.getText(self, "Save Perspective", "Enter Unique name:") + if not ok or not perspective_name: + return + + self.dock_manager.addPerspective(perspective_name) + blocker = QSignalBlocker(self.perspective_combobox) + self.perspective_combobox.clear() + self.perspective_combobox.addItems(self.dock_manager.perspectiveNames()) + self.perspective_combobox.setCurrentText(perspective_name) + +if __name__ == '__main__': + app = QApplication(sys.argv) + + w = MainWindow() + w.show() + app.exec_() diff --git a/examples/centralwidget/mainwindow.cpp b/examples/centralwidget/mainwindow.cpp index f4c7858..9a3caa6 100644 --- a/examples/centralwidget/mainwindow.cpp +++ b/examples/centralwidget/mainwindow.cpp @@ -15,6 +15,8 @@ #include #include #include +#include +#include #include "DockAreaWidget.h" #include "DockAreaTitleBar.h" @@ -24,6 +26,18 @@ using namespace ads; +/** + * Helper function to create an SVG icon + */ +static QIcon svgIcon(const QString& File) +{ + // This is a workaround, because in item views SVG icons are not + // properly scaled and look blurry or pixelate + QIcon SvgIcon(File); + SvgIcon.addPixmap(SvgIcon.pixmap(92)); + return SvgIcon; +} + CMainWindow::CMainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::CMainWindow) @@ -31,12 +45,14 @@ CMainWindow::CMainWindow(QWidget *parent) ui->setupUi(this); CDockManager::setConfigFlag(CDockManager::OpaqueSplitterResize, true); CDockManager::setConfigFlag(CDockManager::XmlCompressionEnabled, false); + CDockManager::setConfigFlag(CDockManager::FocusHighlighting, true); DockManager = new CDockManager(this); // Set central widget - QCalendarWidget* calendar = new QCalendarWidget(); + QPlainTextEdit* w = new QPlainTextEdit(); + w->setPlaceholderText("This is the central editor. Enter your text here."); CDockWidget* CentralDockWidget = new CDockWidget("CentralWidget"); - CentralDockWidget->setWidget(calendar); + CentralDockWidget->setWidget(w); auto* CentralDockArea = DockManager->setCentralWidget(CentralDockWidget); CentralDockArea->setAllowedAreas(DockWidgetArea::OuterDockAreas); @@ -65,8 +81,8 @@ CMainWindow::CMainWindow(QWidget *parent) ui->menuView->addAction(TableDockWidget->toggleViewAction()); QTableWidget* propertiesTable = new QTableWidget(); - table->setColumnCount(3); - table->setRowCount(10); + propertiesTable->setColumnCount(3); + propertiesTable->setRowCount(10); CDockWidget* PropertiesDockWidget = new CDockWidget("Properties"); PropertiesDockWidget->setWidget(propertiesTable); PropertiesDockWidget->setMinimumSizeHintMode(CDockWidget::MinimumSizeHintFromDockWidget); @@ -74,6 +90,8 @@ CMainWindow::CMainWindow(QWidget *parent) PropertiesDockWidget->setMinimumSize(200,150); DockManager->addDockWidget(DockWidgetArea::RightDockWidgetArea, PropertiesDockWidget, CentralDockArea); ui->menuView->addAction(PropertiesDockWidget->toggleViewAction()); + + createPerspectiveUi(); } CMainWindow::~CMainWindow() @@ -81,3 +99,37 @@ CMainWindow::~CMainWindow() delete ui; } + +void CMainWindow::createPerspectiveUi() +{ + SavePerspectiveAction = new QAction("Create Perspective", this); + SavePerspectiveAction->setIcon(svgIcon(":/adsdemo/images/picture_in_picture.svg")); + connect(SavePerspectiveAction, SIGNAL(triggered()), SLOT(savePerspective())); + PerspectiveListAction = new QWidgetAction(this); + PerspectiveComboBox = new QComboBox(this); + PerspectiveComboBox->setSizeAdjustPolicy(QComboBox::AdjustToContents); + PerspectiveComboBox->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + connect(PerspectiveComboBox, SIGNAL(activated(const QString&)), + DockManager, SLOT(openPerspective(const QString&))); + PerspectiveListAction->setDefaultWidget(PerspectiveComboBox); + ui->toolBar->addSeparator(); + ui->toolBar->addAction(PerspectiveListAction); + ui->toolBar->addAction(SavePerspectiveAction); +} + + +void CMainWindow::savePerspective() +{ + QString PerspectiveName = QInputDialog::getText(this, "Save Perspective", "Enter unique name:"); + if (PerspectiveName.isEmpty()) + { + return; + } + + DockManager->addPerspective(PerspectiveName); + QSignalBlocker Blocker(PerspectiveComboBox); + PerspectiveComboBox->clear(); + PerspectiveComboBox->addItems(DockManager->perspectiveNames()); + PerspectiveComboBox->setCurrentText(PerspectiveName); +} + diff --git a/examples/centralwidget/mainwindow.h b/examples/centralwidget/mainwindow.h index cbd05b0..b31c91b 100644 --- a/examples/centralwidget/mainwindow.h +++ b/examples/centralwidget/mainwindow.h @@ -2,6 +2,8 @@ #define MAINWINDOW_H #include +#include +#include #include "DockManager.h" #include "DockAreaWidget.h" @@ -20,13 +22,19 @@ public: ~CMainWindow(); private: - static const QString kTableTopLayout; - static const QString kTableBottomLayout; + QAction* SavePerspectiveAction = nullptr; + QWidgetAction* PerspectiveListAction = nullptr; + QComboBox* PerspectiveComboBox = nullptr; Ui::CMainWindow *ui; ads::CDockManager* DockManager; ads::CDockAreaWidget* StatusDockArea; ads::CDockWidget* TimelineDockWidget; + + void createPerspectiveUi(); + +private slots: + void savePerspective(); }; #endif // MAINWINDOW_H diff --git a/examples/centralwidget/mainwindow.ui b/examples/centralwidget/mainwindow.ui index 135fdd1..f7d3b09 100644 --- a/examples/centralwidget/mainwindow.ui +++ b/examples/centralwidget/mainwindow.ui @@ -30,6 +30,17 @@ + + + toolBar + + + TopToolBarArea + + + false + + diff --git a/examples/simple/simple.py b/examples/simple/simple.py index fe24fc6..b21767d 100644 --- a/examples/simple/simple.py +++ b/examples/simple/simple.py @@ -21,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_manager1 = QtAds.CDockManager(self) + self.dock_manager = QtAds.CDockManager(self) # Create example content label - this can be any application specific # widget @@ -35,41 +35,13 @@ class MainWindow(MainWindowUI, MainWindowBase): 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_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() + self.dock_manager.addDockWidget(QtAds.TopDockWidgetArea, dock_widget) if __name__ == '__main__': diff --git a/setup.py b/setup.py index 1986df7..31546a9 100644 --- a/setup.py +++ b/setup.py @@ -227,7 +227,9 @@ class build_ext(sipdistutils.build_ext): extension.extra_link_args += ['-F' + self.qtconfig.QT_INSTALL_LIBS, '-mmacosx-version-min=10.9'] elif sys.platform == 'linux': - extension.extra_compile_args += ['-std=c++11'] + extension.extra_compile_args += ['-D', 'QT_X11EXTRAS_LIB', '-std=c++11'] + extension.include_dirs += [os.path.join(self.qt_include_dir, 'QtX11Extras')] + extension.libraries += ['Qt5X11Extras' + self.qt_libinfix] return super().swig_sources(sources, extension) diff --git a/sip/DockAreaWidget.sip b/sip/DockAreaWidget.sip index f7f1e4a..8196047 100644 --- a/sip/DockAreaWidget.sip +++ b/sip/DockAreaWidget.sip @@ -28,6 +28,13 @@ protected slots: void toggleView(bool Open); public: + enum eDockAreaFlag + { + HideSingleWidgetTitleBar, + DefaultFlags + }; + typedef QFlags DockAreaFlags; + CDockAreaWidget(ads::CDockManager* DockManager /TransferThis/, ads::CDockContainerWidget* parent /TransferThis/); virtual ~CDockAreaWidget(); ads::CDockManager* dockManager() const; @@ -51,8 +58,13 @@ public: void setAllowedAreas(DockWidgetAreas areas); DockWidgetAreas allowedAreas() const; - void setHideSingleWidgetTitleBar(bool hide); CDockAreaTitleBar* titleBar() const; + + DockAreaFlags dockAreaFlags() const; + void setDockAreaFlags(DockAreaFlags Flags); + void setDockAreaFlag(eDockAreaFlag Flag, bool On); + + bool isCentralWidgetArea() const; public slots: void setCurrentIndex(int index); diff --git a/sip/DockContainerWidget.sip b/sip/DockContainerWidget.sip index ae90e3c..be8e6d7 100644 --- a/sip/DockContainerWidget.sip +++ b/sip/DockContainerWidget.sip @@ -32,6 +32,7 @@ protected: ads::CDockWidget* topLevelDockWidget() const; ads::CDockAreaWidget* topLevelDockArea() const; QList dockWidgets() const; + void updateSplitterHandles(QSplitter* splitter); public: /** diff --git a/sip/DockFocusController.sip b/sip/DockFocusController.sip index 7d8edfd..1f45699 100644 --- a/sip/DockFocusController.sip +++ b/sip/DockFocusController.sip @@ -20,6 +20,7 @@ public: void notifyWidgetOrAreaRelocation(QWidget* RelocatedWidget); void notifyFloatingWidgetDrop(ads::CFloatingDockContainer* FloatingWidget); + ads::CDockWidget* focusedDockWidget() const; public slots: void setDockWidgetFocused(ads::CDockWidget* focusedNow); diff --git a/sip/DockManager.sip b/sip/DockManager.sip index d5d2f7a..b76ee25 100644 --- a/sip/DockManager.sip +++ b/sip/DockManager.sip @@ -135,6 +135,7 @@ protected: ads::CDockOverlay* dockAreaOverlay() const; void notifyWidgetOrAreaRelocation(QWidget* RelocatedWidget); void notifyFloatingWidgetDrop(ads::CFloatingDockContainer* FloatingWidget); + ads::CDockWidget* focusedDockWidget() const; virtual void showEvent(QShowEvent *event); @@ -170,6 +171,8 @@ public: HideSingleCentralWidgetTitleBar, FocusHighlighting, EqualSplitOnInsertion, + FloatingContainerForceNativeTitleBar, + FloatingContainerForceQWidgetTitleBar, DefaultDockAreaButtons, DefaultBaseConfig, DefaultOpaqueConfig, @@ -206,6 +209,8 @@ public: QStringList perspectiveNames() const; void savePerspectives(QSettings& Settings) const; void loadPerspectives(QSettings& Settings); + CDockWidget* centralWidget() const; + CDockAreaWidget* setCentralWidget(CDockWidget* widget /Transfer/); QAction* addToggleViewActionToMenu(QAction* ToggleViewAction /Transfer/, const QString& Group = QString(), const QIcon& GroupIcon = QIcon()); QMenu* viewMenu() const; @@ -226,6 +231,7 @@ signals: void perspectiveOpened(const QString& PerspectiveName); void floatingWidgetCreated(ads::CFloatingDockContainer*); void dockAreaCreated(ads::CDockAreaWidget*); + void dockWidgetAdded(ads::CDockWidget* DockWidget); void dockWidgetAboutToBeRemoved(ads::CDockWidget*); void dockWidgetRemoved(ads::CDockWidget*); void focusedDockWidgetChanged(ads::CDockWidget*, ads::CDockWidget*); diff --git a/sip/DockSplitter.sip b/sip/DockSplitter.sip index d6a5f31..2b567e2 100644 --- a/sip/DockSplitter.sip +++ b/sip/DockSplitter.sip @@ -18,6 +18,7 @@ public: bool hasVisibleContent() const; QWidget* firstWidget() const; QWidget* lastWidget() const; + bool isResizingWithContainer() const; }; diff --git a/sip/DockWidget.sip b/sip/DockWidget.sip index 471f687..5aa4b83 100644 --- a/sip/DockWidget.sip +++ b/sip/DockWidget.sip @@ -85,6 +85,7 @@ public: QAction* toggleViewAction() const; void setToggleViewActionMode(ads::CDockWidget::eToggleViewActionMode Mode); void setMinimumSizeHintMode(ads::CDockWidget::eMinimumSizeHintMode Mode); + bool isCentralWidget() const; void setIcon(const QIcon& Icon); QIcon icon() const; QToolBar* toolBar() const; diff --git a/sip/FloatingDockContainer.sip b/sip/FloatingDockContainer.sip index 4e80db5..0a6d51f 100644 --- a/sip/FloatingDockContainer.sip +++ b/sip/FloatingDockContainer.sip @@ -52,11 +52,20 @@ protected: protected: virtual void changeEvent(QEvent *event); - virtual void moveEvent(QMoveEvent *event); - virtual bool event(QEvent *e); virtual void closeEvent(QCloseEvent *event); virtual void hideEvent(QHideEvent *event); virtual void showEvent(QShowEvent *event); + + %If (WS_MACX) + virtual bool event(QEvent *e); + virtual void moveEvent(QMoveEvent *event); + %End + + %If (WS_X11) + virtual void moveEvent(QMoveEvent *event); + virtual void resizeEvent(QResizeEvent *event); + %End + %If (WS_WIN) virtual bool nativeEvent(const QByteArray &eventType, void *message, long *result); %End @@ -72,6 +81,15 @@ public: bool hasTopLevelDockWidget() const; ads::CDockWidget* topLevelDockWidget() const; QList dockWidgets() const; + + %If (WS_X11) + void onMaximizeRequest(); + void showNormal(bool fixGeometry); + void showMaximized(); + bool isMaximized() const; + void show(); + bool hasNativeTitleBar(); + %End }; }; diff --git a/sip/linux/FloatingWidgetTitleBar.sip b/sip/linux/FloatingWidgetTitleBar.sip index fe37bd3..44a2d38 100644 --- a/sip/linux/FloatingWidgetTitleBar.sip +++ b/sip/linux/FloatingWidgetTitleBar.sip @@ -15,15 +15,24 @@ protected: virtual void mousePressEvent(QMouseEvent *ev); virtual void mouseReleaseEvent(QMouseEvent *ev); virtual void mouseMoveEvent(QMouseEvent *ev); + virtual void mouseDoubleClickEvent(QMouseEvent *event); + + void setMaximizeIcon(const QIcon& Icon); + QIcon maximizeIcon() const; + void setNormalIcon(const QIcon& Icon); + QIcon normalIcon() const; public: explicit CFloatingWidgetTitleBar(CFloatingDockContainer *parent /TransferThis/ = 0); virtual ~CFloatingWidgetTitleBar(); void enableCloseButton(bool Enable); void setTitle(const QString &Text); + void updateStyle(); + void setMaximizedIcon(bool maximized); signals: void closeRequested(); + void maximizeRequested(); }; }; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6e31d27..0bd721d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,6 +1,9 @@ cmake_minimum_required(VERSION 3.5) project(QtAdvancedDockingSystem LANGUAGES CXX VERSION ${VERSION_SHORT}) -find_package(Qt5 5.5 COMPONENTS Core Gui Widgets X11Extras REQUIRED) +find_package(Qt5 5.5 COMPONENTS Core Gui Widgets REQUIRED) +if (UNIX) + find_package(Qt5 5.5 COMPONENTS X11Extras REQUIRED) +endif() set(CMAKE_INCLUDE_CURRENT_DIR ON) if(BUILD_STATIC) set(CMAKE_STATIC_LIBRARY_SUFFIX "_static${CMAKE_STATIC_LIBRARY_SUFFIX}") @@ -57,6 +60,9 @@ else() target_compile_definitions(qtadvanceddocking PRIVATE ADS_SHARED_EXPORT) endif() target_link_libraries(qtadvanceddocking PUBLIC Qt5::Core Qt5::Gui Qt5::Widgets) +if (UNIX) + target_link_libraries(qtadvanceddocking PUBLIC Qt5::X11Extras) +endif() set_target_properties(qtadvanceddocking PROPERTIES AUTOMOC ON AUTORCC ON diff --git a/src/DockContainerWidget.cpp b/src/DockContainerWidget.cpp index a10495e..e045a31 100644 --- a/src/DockContainerWidget.cpp +++ b/src/DockContainerWidget.cpp @@ -1207,7 +1207,7 @@ void DockContainerWidgetPrivate::dumpRecursive(int level, QWidget* widget) std::cout << (const char*)buf << (DockArea->isHidden() ? " " : "v") << (DockArea->openDockWidgetsCount() > 0 ? " " : "c") - << " DockArea" << std::endl; + << " DockArea " << "[hs: " << DockArea->sizePolicy().horizontalStretch() << ", vs: " << DockArea->sizePolicy().verticalStretch() << "]" << std::endl; buf.fill(' ', (level + 1) * 4); for (int i = 0; i < DockArea->dockWidgetsCount(); ++i) { diff --git a/src/DockFocusController.cpp b/src/DockFocusController.cpp index c488288..77e73c1 100644 --- a/src/DockFocusController.cpp +++ b/src/DockFocusController.cpp @@ -381,6 +381,13 @@ void CDockFocusController::onStateRestored() } } + +//========================================================================== +CDockWidget* CDockFocusController::focusedDockWidget() const +{ + return d->FocusedDockWidget.data(); +} + } // namespace ads //--------------------------------------------------------------------------- diff --git a/src/DockFocusController.h b/src/DockFocusController.h index 8adfab5..f6b0af5 100644 --- a/src/DockFocusController.h +++ b/src/DockFocusController.h @@ -77,6 +77,12 @@ public: */ void notifyFloatingWidgetDrop(CFloatingDockContainer* FloatingWidget); + /** + * Returns the dock widget that has focus style in the ui or a nullptr if + * not dock widget is painted focused. + */ + CDockWidget* focusedDockWidget() const; + public slots: /** * Request a focus change to the given dock widget diff --git a/src/DockManager.cpp b/src/DockManager.cpp index d322dfe..16b2d01 100644 --- a/src/DockManager.cpp +++ b/src/DockManager.cpp @@ -279,6 +279,27 @@ bool DockManagerPrivate::restoreStateFromXml(const QByteArray &state, int versi int DockContainers = s.attributes().value("Containers").toInt(); #endif ADS_PRINT(DockContainers); + + if (CentralWidget) + { + const auto CentralWidgetAttribute = s.attributes().value("CentralWidget"); + // If we have a central widget but a state without central widget, then + // something is wrong. + if (CentralWidgetAttribute.isEmpty()) + { + qWarning() << "Dock manager has central widget but saved state does not have central widget."; + return false; + } + + // If the object name of the central widget does not match the name of the + // saved central widget, the something is wrong + if (CentralWidget->objectName() != CentralWidgetAttribute.toString()) + { + qWarning() << "Object name of central widget does not match name of central widget in saved state."; + return false; + } + } + int DockContainerCount = 0; while (s.readNextStartElement()) { @@ -405,7 +426,6 @@ bool DockManagerPrivate::restoreState(const QByteArray& State, int version) return false; } - CentralWidget = nullptr; // Hide updates of floating widgets from use hideFloatingWidgets(); markDockWidgetsDirty(); @@ -419,6 +439,7 @@ bool DockManagerPrivate::restoreState(const QByteArray& State, int version) restoreDockWidgetsOpenState(); restoreDockAreasIndices(); emitTopLevelEvents(); + _this->dumpLayout(); return true; } @@ -636,6 +657,10 @@ QByteArray CDockManager::saveState(int version) const s.writeAttribute("Version", QString::number(CurrentVersion)); s.writeAttribute("UserVersion", QString::number(version)); s.writeAttribute("Containers", QString::number(d->Containers.count())); + if (d->CentralWidget) + { + s.writeAttribute("CentralWidget", d->CentralWidget->objectName()); + } for (auto Container : d->Containers) { Container->saveState(s); @@ -706,6 +731,7 @@ CFloatingDockContainer* CDockManager::addDockWidgetFloating(CDockWidget* Dockwid { d->UninitializedFloatingWidgets.append(FloatingWidget); } + emit dockWidgetAdded(Dockwidget); return FloatingWidget; } @@ -732,7 +758,9 @@ CDockAreaWidget* CDockManager::addDockWidget(DockWidgetArea area, CDockWidget* Dockwidget, CDockAreaWidget* DockAreaWidget) { d->DockWidgetsMap.insert(Dockwidget->objectName(), Dockwidget); - return CDockContainerWidget::addDockWidget(area, Dockwidget, DockAreaWidget); + auto AreaOfAddedDockWidget = CDockContainerWidget::addDockWidget(area, Dockwidget, DockAreaWidget); + emit dockWidgetAdded(Dockwidget); + return AreaOfAddedDockWidget; } @@ -901,10 +929,20 @@ CDockAreaWidget* CDockManager::setCentralWidget(CDockWidget* widget) return nullptr; } - // Setting a new central widget is now allowed if there is alread a central - // widget + // Setting a new central widget is now allowed if there is already a central + // widget or if there are already other dock widgets if (d->CentralWidget) { + qWarning("Setting a central widget not possible because there is already a central widget."); + return nullptr; + } + + // Setting a central widget is now allowed if there are already other + // dock widgets. + if (!d->DockWidgetsMap.isEmpty()) + { + qWarning("Setting a central widget not possible - the central widget need to be the first " + "dock widget that is added to the dock manager."); return nullptr; } @@ -1042,6 +1080,20 @@ void CDockManager::setDockWidgetFocused(CDockWidget* DockWidget) } +//=========================================================================== +CDockWidget* CDockManager::focusedDockWidget() const +{ + if (!d->FocusController) + { + return nullptr; + } + else + { + return d->FocusController->focusedDockWidget(); + } +} + + } // namespace ads //--------------------------------------------------------------------------- diff --git a/src/DockManager.h b/src/DockManager.h index a987990..8832415 100644 --- a/src/DockManager.h +++ b/src/DockManager.h @@ -398,9 +398,12 @@ public: * 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 + * the created dock 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. + * \note Setting a central widget is only possible if no other dock widgets + * have been registered before. That means, this function should be the + * first function that you call before you add other dock widgets. * \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. @@ -476,6 +479,14 @@ public: bool eventFilter(QObject *obj, QEvent *e) override; #endif + /** + * Returns the dock widget that has focus style in the ui or a nullptr if + * not dock widget is painted focused. + * If the flag FocusHighlighting is disabled, this function always returns + * nullptr. + */ + CDockWidget* focusedDockWidget() const; + public slots: /** * Opens the perspective with the given name. @@ -544,6 +555,12 @@ signals: */ void dockAreaCreated(ads::CDockAreaWidget* DockArea); + /** + * This signal is emitted if a dock widget has been added to this + * dock manager instance. + */ + void dockWidgetAdded(ads::CDockWidget* DockWidget); + /** * This signal is emitted just before the given dock widget is removed * from the dock manager diff --git a/src/DockWidgetTab.cpp b/src/DockWidgetTab.cpp index 6d80b1f..f4a1ae9 100644 --- a/src/DockWidgetTab.cpp +++ b/src/DockWidgetTab.cpp @@ -146,6 +146,19 @@ struct DockWidgetTabPrivate CloseButton->setVisible(DockWidgetClosable && TabHasCloseButton); } + /** + * Update the size policy of the close button depending on the + * RetainTabSizeWhenCloseButtonHidden feature + */ + void updateCloseButtonSizePolicy() + { + auto Features = DockWidget->features(); + auto SizePolicy = CloseButton->sizePolicy(); + SizePolicy.setRetainSizeWhenHidden(Features.testFlag(CDockWidget::DockWidgetClosable) + && testConfigFlag(CDockManager::RetainTabSizeWhenCloseButtonHidden)); + CloseButton->setSizePolicy(SizePolicy); + } + template IFloatingWidget* createFloatingWidget(T* Widget, bool OpaqueUndocking) { @@ -200,7 +213,7 @@ void DockWidgetTabPrivate::createLayout() CloseButton->setObjectName("tabCloseButton"); internal::setButtonIcon(CloseButton, QStyle::SP_TitleBarCloseButton, TabCloseIcon); CloseButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - _this->onDockWidgetFeaturesChanged(); + updateCloseButtonSizePolicy(); internal::setToolTip(CloseButton, QObject::tr("Close Tab")); _this->connect(CloseButton, SIGNAL(clicked()), SIGNAL(closeRequested())); @@ -473,7 +486,7 @@ bool CDockWidgetTab::isActiveTab() const //============================================================================ void CDockWidgetTab::setActiveTab(bool active) { - d->updateCloseButtonVisibility(active); + d->updateCloseButtonVisibility(active); // Focus related stuff if (CDockManager::testConfigFlag(CDockManager::FocusHighlighting) && !d->DockWidget->dockManager()->isRestoringState()) @@ -656,12 +669,8 @@ bool CDockWidgetTab::event(QEvent *e) //============================================================================ void CDockWidgetTab::onDockWidgetFeaturesChanged() { - auto Features = d->DockWidget->features(); - auto SizePolicy = d->CloseButton->sizePolicy(); - SizePolicy.setRetainSizeWhenHidden(Features.testFlag(CDockWidget::DockWidgetClosable) - && d->testConfigFlag(CDockManager::RetainTabSizeWhenCloseButtonHidden)); - d->CloseButton->setSizePolicy(SizePolicy); - d->updateCloseButtonVisibility(isActiveTab()); + d->updateCloseButtonSizePolicy(); + d->updateCloseButtonVisibility(isActiveTab()); } diff --git a/src/FloatingDockContainer.cpp b/src/FloatingDockContainer.cpp index 09b31bc..55979f3 100644 --- a/src/FloatingDockContainer.cpp +++ b/src/FloatingDockContainer.cpp @@ -752,7 +752,7 @@ bool CFloatingDockContainer::nativeEvent(const QByteArray &eventType, void *mess case WM_NCLBUTTONDOWN: if (msg->wParam == HTCAPTION && d->isState(DraggingInactive)) { - ADS_PRINT("CFloatingDockContainer::nativeEvent WM_NCLBUTTONDOWN" << e->type()); + ADS_PRINT("CFloatingDockContainer::nativeEvent WM_NCLBUTTONDOWN"); d->DragStartPos = pos(); d->setState(DraggingMousePressed); } @@ -765,7 +765,7 @@ bool CFloatingDockContainer::nativeEvent(const QByteArray &eventType, void *mess case WM_ENTERSIZEMOVE: if (d->isState(DraggingMousePressed)) { - ADS_PRINT("CFloatingDockContainer::nativeEvent WM_ENTERSIZEMOVE" << e->type()); + ADS_PRINT("CFloatingDockContainer::nativeEvent WM_ENTERSIZEMOVE"); d->setState(DraggingFloatingWidget); d->updateDropOverlays(QCursor::pos()); } @@ -774,7 +774,7 @@ bool CFloatingDockContainer::nativeEvent(const QByteArray &eventType, void *mess case WM_EXITSIZEMOVE: if (d->isState(DraggingFloatingWidget)) { - ADS_PRINT("CFloatingDockContainer::nativeEvent WM_EXITSIZEMOVE" << e->type()); + ADS_PRINT("CFloatingDockContainer::nativeEvent WM_EXITSIZEMOVE"); if (GetAsyncKeyState(VK_ESCAPE) & 0x8000) { d->handleEscapeKey(); diff --git a/src/src.pro b/src/src.pro index 9cd81f4..db26109 100644 --- a/src/src.pro +++ b/src/src.pro @@ -69,7 +69,7 @@ SOURCES += \ DockFocusController.cpp -unix { +unix:!macx { HEADERS += linux/FloatingWidgetTitleBar.h SOURCES += linux/FloatingWidgetTitleBar.cpp QT += x11extras