KDAB / KDDockWidgets

KDAB's Dock Widget Framework for Qt
https://www.kdab.com/development-resources/qt-tools/kddockwidgets
Other
754 stars 163 forks source link

Issues with nested MainWindows / DockWidgets #464

Closed bbc131 closed 5 months ago

bbc131 commented 7 months ago

I have an use case with nested MainWindows and DockWidgets. Overall this works good, but I found a two bugs and have some further questions for new features, which I would like to address in this issue. Because they all appear at the identical use case, I create only this one present issue.

The main idea of my use case is the following (code can be found at the bottom), see also the following screenshot. I will have an arbitrary number of dynamically constructed/deleted DockWidgets, which can dock in the main window of the application. I call them "SuperDock". For simplicity, I have only two of them in the example, "SuperDock-1" and "..-2". Each SuperDock will contain a fixed number of DockWidgets, which can only be docked in this specific SuperDock. I call them "SubDock" in the example and we have "SubDock-A" and "..-B" belonging to SuperDock-1 and "SubDock-C" and "..-D" belonging to "SuperDock-2", respectively.

01_initially

I have Qt 5.15 and built the example with the KDDockWidgets main branch from yesterday, commit 67046edc99.

So far this works fine, but here are the problems:

(1) Bug: Undock buttons don't work

If I undock one of the SuperDocks (with both SubDocks staying as initially), the undock buttons (marked red in the screenshot below) of those two SubDocks don't work anymore. Nothing happens, if I click them. When I dock the whole SuperDock again into the application main window, then the buttons work again.

02_Undock_buttons_dont_work

(2) Bug: Close buttons at the tabs

An important point in my use case is, that the SuperDocks shall be closable, but the SubDocks don't. I set the flag Config::Flag_TabsHaveCloseButton and make the SubDocks non closable, see also the set_as_sub_dock function in the example code. This means I don't see the close buttons on the tabs of SubDocks, which I do see for the SuperDocks, this is all fine. But If I drag the red SubDock onto the blue one, such that they are tabbed, then drag and dock it again as initially (beside the blue one) and then drag the blue one onto the red one, such that they are tabbed again, then there appear close buttons on the tabs. See red marks on the screenshot. Initially they are not there, as one can see for the other two SubDocks C and D, marked green.

03_Close_buttons_on_tab

(3) Hide instead of disable close buttons

Setting the dockwidget as non closable only disables the close buttons of the SubDocks but doesn't hide them, see the first screenshot at the top. I guess the reason for this is, that this button stays the same also if there are multiple docks tabbed. In this case there could be some closable and some don't. For my case, I don't have them mixed, would it be possible to make them disappear instead of hidden by a config flag?

(4) Show-Buttons-On-Tab-Bar-If-TitleBar-Hidden per MainWindow

Because I want to have the SuperDocks closable but the SubDocks not, I also would like to set the flag Config::Flag_ShowButtonsOnTabBarIfTitleBarHidden differently for my "SuperDocks" and "SubDocks". Is there any chance, that this can be implemented? My first thought would be some flags specified per mainwindow. Actually, this has nothing to do with my specific use case of nested mainwindows, but with the fact, that the flags work only application-wide.

Example code (modified kddockwidgets example 'minimal')

#include "kddockwidgets/Config.h"
#include <kddockwidgets/MainWindow.h>
#include <kddockwidgets/DockWidget.h>
#include "kddockwidgets/core/DockWidget.h"
#include "kddockwidgets/core/TitleBar.h"
#include "kddockwidgets/core/Group.h"
#include "kddockwidgets/core/Stack.h"
#include "kddockwidgets/qtwidgets/views/TitleBar.h"
#include "kddockwidgets/qtwidgets/views/Stack.h"
#include "kddockwidgets/qtwidgets/ViewFactory.h"

#include <QStyleFactory>
#include <QLabel>
#include <QApplication>

// clazy:excludeall=qstring-allocations

using namespace KDDockWidgets;

int main(int argc, char **argv)
{
    QApplication app(argc, argv);

    KDDockWidgets::initFrontend(KDDockWidgets::FrontendType::QtWidgets);

    auto &config = Config::self();

    config.setFlags(config.flags()
                    | KDDockWidgets::Config::Flag_HideTitleBarWhenTabsVisible
                    | KDDockWidgets::Config::Flag_TabsHaveCloseButton
                    | KDDockWidgets::Config::Flag_CloseOnlyCurrentTab
                    | KDDockWidgets::Config::Flag_ShowButtonsOnTabBarIfTitleBarHidden // I want this only for the "super" docks
    );

    // Create application main window
    KDDockWidgets::QtWidgets::MainWindow appMainWindow(QStringLiteral("Application MainWindow"));
    appMainWindow.setWindowTitle("Application Main Window");
    appMainWindow.resize(600, 200);
    appMainWindow.show();

    // Create two "super" dock widgets with a main window each
    /* The "super" docks shall be closable */
    KDDockWidgets::QtWidgets::MainWindow mw1(QStringLiteral("MainWindow1"));
    mw1.setAffinities({ "MainWindow1" });
    mw1.setWindowTitle("Main Window 1");

    KDDockWidgets::QtWidgets::MainWindow mw2(QStringLiteral("MainWindow2"));
    mw2.setAffinities({ "MainWindow2" });
    mw2.setWindowTitle("Main Window 2");

    auto superdock1 = new KDDockWidgets::QtWidgets::DockWidget(QStringLiteral("SuperDock-1 (A/B)"));
    superdock1->setAttribute(Qt::WA_DeleteOnClose);
    superdock1->setWidget(&mw1);

    auto superdock2 = new KDDockWidgets::QtWidgets::DockWidget(QStringLiteral("SuperDock-2 (C/D)"));
    superdock2->setAttribute(Qt::WA_DeleteOnClose);
    superdock2->setWidget(&mw2);

    appMainWindow.addDockWidget(superdock1, KDDockWidgets::Location_OnLeft);
    appMainWindow.addDockWidget(superdock2, KDDockWidgets::Location_OnRight);

    // Create four "sub" dock widgets, two for each "super" dock / main window (with affinities to them)
    /* The "super" docks shall NOT be closable */

    auto set_color = [](QLabel *label, QString color) {
        auto pal = label->palette();
        pal.setColor(QPalette::Window, QColor(color));
        label->setPalette(pal);
        label->setAutoFillBackground(true);
    };

    auto set_as_sub_dock = [](QtWidgets::DockWidget *dockwidget) {
        // Disables close button in title bar
        dockwidget->setOptions(KDDockWidgets::DockWidgetOption::DockWidgetOption_NotClosable);

        // Hides close button in tab
        auto group = dockwidget->group();
        if (group) 
        {
            auto stack = group->stack();
            if (stack) 
            {
                auto stack_view = stack->asView();
                if (stack_view) 
                {
                    auto tab_widget = dynamic_cast<QTabWidget *>(stack_view);
                    if (tab_widget)
                    {
                        tab_widget->setTabsClosable(false);
                    }
                }
            }
        }
    };

    auto subdockA = new KDDockWidgets::QtWidgets::DockWidget(QStringLiteral("SubDock-A (1)"));
    subdockA->setAffinityName("MainWindow1");
    auto subwidgetA = new QLabel("subwidget-A (1)");
    subwidgetA->setAlignment(Qt::AlignCenter);
    set_color(subwidgetA, QString("red"));
    subdockA->setWidget(subwidgetA);

    auto subdockB = new KDDockWidgets::QtWidgets::DockWidget(QStringLiteral("SubDock-B (1)"));
    subdockB->setAffinityName("MainWindow1");
    auto subwidgetB = new QLabel("subwidget-B (1)");
    subwidgetB->setAlignment(Qt::AlignCenter);
    set_color(subwidgetB, QString("blue"));
    subdockB->setWidget(subwidgetB);

    auto subdockC = new KDDockWidgets::QtWidgets::DockWidget(QStringLiteral("SubDock-C (2)"));
    subdockC->setAffinityName("MainWindow2");
    auto subwidgetC = new QLabel("subwidget-C (2)");
    subwidgetC->setAlignment(Qt::AlignCenter);
    set_color(subwidgetC, QString("green"));
    subdockC->setWidget(subwidgetC);

    auto subdockD = new KDDockWidgets::QtWidgets::DockWidget(QStringLiteral("SubDock-D (2)"));
    subdockD->setAffinityName("MainWindow2");
    auto subwidgetD = new QLabel("subwidget-D (2)");
    subwidgetD->setAlignment(Qt::AlignCenter);
    set_color(subwidgetD, QString("magenta"));
    subdockD->setWidget(subwidgetD);

    mw1.addDockWidget(subdockA, KDDockWidgets::Location_OnLeft);
    mw1.addDockWidget(subdockB, KDDockWidgets::Location_OnRight);
    mw2.addDockWidget(subdockC, KDDockWidgets::Location_OnLeft);
    mw2.addDockWidget(subdockD, KDDockWidgets::Location_OnRight);

    set_as_sub_dock(subdockA);
    set_as_sub_dock(subdockB);
    set_as_sub_dock(subdockC);
    set_as_sub_dock(subdockD);

    return app.exec();
}
bbc131 commented 7 months ago

Is there a schedule for v2.1 or do you already know, when you want to start working on these issues?

iamsergio commented 7 months ago

Is there a schedule for v2.1 or do you already know, when you want to start working on these issues?

Next week I'll have some for this ticket, I've been busy these last weeks. 2.1 is around march, but you can use main branch sooner

iamsergio commented 7 months ago
  1. is fixed
  2. For number 2, it's not recommended to modify the QTabWidget, which is created/destroyed at will by KDDW, meaning your setup code might not be running in all cases. A solution can be to subclass your own QTabWidget:
#include <kddockwidgets/qtwidgets/ViewFactory.h>

namespace {

class MyTabWidget : public QtWidgets::Stack
{
public:
    using QtWidgets::Stack::Stack;

    void tabInserted(int index) override
    {
        QtWidgets::Stack::tabInserted(index);

        if (index == 0) { // only needs to run once
            if (auto dock = qobject_cast<KDDockWidgets::QtWidgets::DockWidget *>(widget(0))) {
                const bool isNonClosable = dock->options() & KDDockWidgets::DockWidgetOption_NotClosable;
                setTabsClosable(!isNonClosable);
            }
        }
    }
};

class CustomWidgetFactory : public KDDockWidgets::QtWidgets::ViewFactory
{

public:
    Core::View *createStack(Core::Stack *controller, Core::View *parent) const
    {
        return new MyTabWidget(controller, QtCommon::View_qt::asQWidget(parent));
    }
};
    auto &config = Config::self();
    config.setViewFactory(new CustomWidgetFactory());

But be sure to make the dockwidget nonclosable in its CTOR:

auto subdockA = new KDDockWidgets::QtWidgets::DockWidget(QStringLiteral("SubDock-A (1)"), KDDockWidgets::DockWidgetOption::DockWidgetOption_NotClosable);

As the tab widget might be created very early.

  1. and 4. will get to it soon
iamsergio commented 6 months ago

For 3. Some API is needed, I guess at the DockWidget level

iamsergio commented 6 months ago
  1. is done, see example in examples/dockwidgets/MyViewFactory.cpp
iamsergio commented 6 months ago
  1. Not entirely sure yet
iamsergio commented 6 months ago

Flag_ShowButtonsOnTabBarIfTitleBarHidden implies Flag_HideTitleBarWhenTabsVisible.

First of all, is it OK if Flag_HideTitleBarWhenTabsVisible remains global, or you need it differently depending on which main window it is ?

If that's the case, then instead of making Flag_ShowButtonsOnTabBarIfTitleBarHidden non-global (not easy), I rather give you API to control the button visibility

bbc131 commented 6 months ago

Solution to 3. looks good.

I think 4. can also be solved by extending the API similar to (3). Then all the config flags can remain global. The only reason for my request for non-global flags is to make the close-button disappear, so if I can hide them via some API, this is fine for me.

iamsergio commented 5 months ago
  1. Is fixed by 8fd5a172ca65558edf860363fd0fa9330c0dd958

You'll need something like:

class Stack464 : public QtWidgets::Stack
{
public:
    using QtWidgets::Stack::Stack;
    void init() override
    {
        m_stack->setHideDisabledButtons(TitleBarButtonType::Close);

        QtWidgets::Stack::init(); // important
    }
};

class ViewFactory464 : public QtWidgets::ViewFactory
{
public:
    Core::View *createStack(Core::Stack *controller, Core::View *parent) const override
    {
        return new Stack464(controller, QtCommon::View_qt::asQWidget(parent));
    }
};
(...)
config.setViewFactory(new ViewFactory464());