2026-04-06 00:20:51 -05:00

248 lines
6.4 KiB
C++

/*
This file is part of KDDockWidgets.
SPDX-FileCopyrightText: 2020 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
Author: Sérgio Martins <sergio.martins@kdab.com>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only
Contact KDAB at <info@kdab.com> for commercial licensing options.
*/
#include "Stack.h"
#include "KDDockWidgets.h"
#include "Stack_p.h"
#include "Config.h"
#include "TitleBar.h"
#include "ViewFactory.h"
#include "Logging_p.h"
#include "Utils_p.h"
#include "WindowBeingDragged_p.h"
#include "DockWidget_p.h"
#include "TabBar.h"
#include "Group.h"
#include "FloatingWindow.h"
#include "ObjectGuard_p.h"
#include "views/StackViewInterface.h"
using namespace KDDockWidgets;
using namespace KDDockWidgets::Core;
Stack::Stack(Group *group, StackOptions options)
: Controller(ViewType::Stack, Config::self().viewFactory()->createStack(this, group->view()))
, Draggable(view(),
Config::self().flags()
& (Config::Flag_HideTitleBarWhenTabsVisible | Config::Flag_AlwaysShowTabs))
, d(new Private(group, options, this))
{
// needs to be initialized out of Private(), as tabbar view's init will call into stack's private
d->m_tabBar = new TabBar(this);
view()->init();
}
Stack::~Stack()
{
delete d->m_tabBar;
delete d;
}
StackOptions Stack::options() const
{
return d->m_options;
}
bool Stack::isPositionDraggable(Point p) const
{
if (auto svi = dynamic_cast<Core::StackViewInterface *>(view()))
return svi->isPositionDraggable(p);
return false;
}
void Stack::addDockWidget(DockWidget *dock)
{
insertDockWidget(dock, numDockWidgets());
}
bool Stack::insertDockWidget(DockWidget *dock, int index)
{
assert(dock);
index = KDDockWidgets::bound(0, index, numDockWidgets());
if (contains(dock)) {
KDDW_ERROR("Refusing to add already existing widget");
return false;
}
ObjectGuard<Group> oldFrame = dock->d->group();
d->m_tabBar->insertDockWidget(index, dock, dock->icon(IconPlace::TabBar), dock->title());
d->m_tabBar->setCurrentIndex(index);
if (oldFrame && oldFrame->beingDeletedLater()) {
// give it a push and delete it immediately.
// Having too many deleteLater() puts us in an inconsistent state. For example if
// LayoutSaver::saveState() would to be called while the Frame hadn't been deleted yet it
// would count with that group unless hacks. Also the unit-tests are full of
// waitForDeleted() due to deleteLater.
// Ideally we would just remove the deleteLater from Group.cpp, but QStack::insertTab()
// would crash, as it accesses the old tab-widget we're stealing from
delete oldFrame;
}
return true;
}
bool Stack::contains(DockWidget *dw) const
{
return d->m_tabBar->indexOfDockWidget(dw) != -1;
}
Group *Stack::group() const
{
return d->m_group;
}
std::unique_ptr<WindowBeingDragged> Stack::makeWindow()
{
// This is called when using Flag_HideTitleBarWhenTabsVisible
// For detaching individual tabs, TabBar::makeWindow() is called.
if (auto fw = view()->rootView()->asFloatingWindowController()) {
if (fw->hasSingleGroup()) {
// We're already in a floating window, and it only has 1 dock widget.
// So there's no detachment to be made, we just move the window.
return std::make_unique<WindowBeingDragged>(fw, this);
}
}
Rect r = d->m_group->view()->geometry();
const Point globalPoint = view()->mapToGlobal(Point(0, 0));
auto floatingWindow = new FloatingWindow(d->m_group, {});
r.moveTopLeft(globalPoint);
floatingWindow->setSuggestedGeometry(r, SuggestedGeometryHint_GeometryIsFromDocked);
floatingWindow->view()->show();
return std::make_unique<WindowBeingDragged>(floatingWindow, this);
}
bool Stack::isWindow() const
{
if (auto fw = view()->rootView()->asFloatingWindowController()) {
// Case of dragging via the tab widget when the title bar is hidden
return fw->hasSingleGroup();
}
return false;
}
Core::DockWidget *Stack::singleDockWidget() const
{
if (d->m_group->hasSingleDockWidget()) {
const auto dockWidgets = d->m_group->dockWidgets();
return dockWidgets.first();
}
return nullptr;
}
bool Stack::isMDI() const
{
return d->m_group && d->m_group->isMDI();
}
bool Stack::onMouseDoubleClick(Point localPos)
{
// User clicked the empty space of the tab widget and we don't have title bar
// We float the entire group.
if (!(Config::self().flags() & Config::Flag_HideTitleBarWhenTabsVisible)
|| tabBar()->dockWidgetAt(localPos))
return false;
Group *group = this->group();
// When using MainWindowOption_HasCentralFrame. The central group is never detachable.
if (group->isCentralGroup())
return false;
if (FloatingWindow *fw = group->floatingWindow()) {
if (fw->hasSingleGroup()) {
// Window is floating, let's restore it to its main window
// Minor hack: Even though our titlebar is hidden, all the logic for float/unfloat
// is in core/TitleBar.cpp, so just call that.
fw->titleBar()->onFloatClicked();
} else {
makeWindow();
}
return true;
} else if (group->isInMainWindow()) {
makeWindow();
return true;
}
return false;
}
void Stack::setTabBarAutoHide(bool is)
{
if (is == d->m_tabBarAutoHide)
return;
d->m_tabBarAutoHide = is;
d->tabBarAutoHideChanged.emit(is);
}
bool Stack::tabBarAutoHide() const
{
return d->m_tabBarAutoHide;
}
Core::TabBar *Stack::tabBar() const
{
return d->m_tabBar;
}
int Stack::numDockWidgets() const
{
return d->m_tabBar->numDockWidgets();
}
void Stack::setDocumentMode(bool is)
{
dynamic_cast<Core::StackViewInterface *>(view())->setDocumentMode(is);
}
void Stack::setHideDisabledButtons(TitleBarButtonTypes types)
{
if (d->m_buttonsToHideIfDisabled != types) {
d->m_buttonsToHideIfDisabled = types;
d->buttonsToHideIfDisabledChanged.emit();
}
}
bool Stack::buttonHidesIfDisabled(TitleBarButtonType type) const
{
return d->m_buttonsToHideIfDisabled & type;
}
bool Stack::dragCanStart(Point pressPos, Point pos) const
{
if (!Draggable::dragCanStart(pressPos, pos))
return false;
if (d->m_group && d->m_group->isCentralGroup()) {
return false;
}
return true;
}