仿Visual Studio灵活且可停靠的小部件基于Qt5 MSVC开发

发布于:2025-07-12 ⋅ 阅读:(16) ⋅ 点赞:(0)

这个项目是一个基于 Qt 框架的MSVC项目,主要用于实现灵活的窗口布局和管理。项目结构如下:

给出部分代码:

FlexWidget

FlexWidget 是一个核心组件,用于管理窗口布局。它支持多种视图模式和功能,如浮动窗口、停靠窗口等。

FlexManager

FlexManager 用于管理所有的 FlexWidget 和 DockWidget 实例。它提供了创建、销毁和管理这些窗口的方法。

#include "QtFlexManager.h"
#include "QtFlexWidget.h"
#include "QtDockWidget.h"
#include "QtDockSite.h"
#include <QtCore/QAbstractNativeEventFilter>
#include <QtCore/QVariant>
#include <QtCore/QDebug>
#include <QtCore/QUuid>
#include <QtCore/QJsonObject>
#include <QtCore/QJsonArray>
#include <QtCore/QJsonDocument>
#include <QtGui/QIcon>
#include <QtWidgets/QApplication>

#ifdef Q_OS_WIN
#include <qt_windows.h>
#endif

int Flex::Update = QEvent::registerEventType();

#ifdef Q_OS_WIN
WId topLevelWindowAt(QWidget* widget, const QPoint& pos)
{
    WId hTmp = 0;
    HWND hWnd = GetWindow(reinterpret_cast<HWND>(widget->effectiveWinId()), GW_HWNDNEXT);
    while (hWnd != nullptr && reinterpret_cast<HWND>(hTmp) == nullptr)
    {
        POINT pnt = { pos.x(), pos.y() };
        ScreenToClient(hWnd, &pnt);
        hTmp = reinterpret_cast<WId>(ChildWindowFromPoint(hWnd, pnt));
        hWnd = GetWindow(hWnd, GW_HWNDNEXT);
    }
    return reinterpret_cast<WId>(GetAncestor(reinterpret_cast<HWND>(hTmp), GA_ROOT));
}
#endif

DockSite* getDockSite(QWidget* widget)
{
    for (DockSite* site = nullptr; widget && !site; widget = widget->parentWidget())
    {
        if ((site = qobject_cast<DockSite*>(widget)) != nullptr)
        {
            return site;
        }
    }
    return nullptr;
}

namespace
{
    typedef std::tuple<QByteArray, int> SnapshotNode;
    typedef QMap<int, SnapshotNode> SnapshotList;
    typedef std::tuple<QString, QString, int> SnapshotItem;
    typedef QMap<QString, SnapshotItem> SnapshotDict;
}

class FlexManagerImpl : public QAbstractNativeEventFilter
{
public:
    FlexManagerImpl() : _ready(false)
    {
    }

public:
    bool nativeEventFilter(const QByteArray &eventType, void *message, long *result);

public:
    QString flexWidgetName(DockWidget* dockWidget) const;
    QString flexWidgetName(FlexWidget* flexWidget) const;

public:
    int generate() const;

public:
    bool equalIdentifer(const QString& id1, const QString& id2) const;

public:
    QString _flexWidgetDestorying;
    QString _dockWidgetDestorying;
    QList<FlexWidget*> _flexWidgets;
    QList<DockWidget*> _dockWidgets;
    QList<QIcon> _buttonIcons;
    SnapshotList _snapshotList;
    SnapshotDict _snapshotDict;
    bool _ready;
};

bool FlexManagerImpl::nativeEventFilter(const QByteArray &eventType, void *message, long *result)
{
    return false;
}

QString FlexManagerImpl::flexWidgetName(DockWidget* dockWidget) const
{
    if (_flexWidgetDestorying.isEmpty())
    {
        return dockWidget->flexWidgetName();
    }
    else
    {
        return _flexWidgetDestorying;
    }
}

QString FlexManagerImpl::flexWidgetName(FlexWidget* flexWidget) const
{
    return flexWidget->objectName();
}

int FlexManagerImpl::generate() const
{
    static int i = 0; return i++;
}

bool FlexManagerImpl::equalIdentifer(const QString& id1, const QString& id2) const
{
    QStringList parts1 = id1.split(",");
    QStringList parts2 = id2.split(",");
    if (parts1.size() != parts2.size())
    {
        return false;
    }
    if (parts1[0] != parts2[0])
    {
        return false;
    }
    for (int i = 1; i < parts1.size() - 1; i += 2)
    {
        if (parts1[i] != parts2[i])
        {
            return false;
        }
    }
    return true;
}

FlexManager::FlexManager() : impl(new FlexManagerImpl)
{
    Q_ASSERT(qApp != nullptr);
    connect(qApp, SIGNAL(focusChanged(QWidget*, QWidget*)), SLOT(on_app_focusChanged(QWidget*, QWidget*)));
    connect(this, SIGNAL(guiderHover(FlexWidget*, QWidget*)), SLOT(on_flexWidget_guiderHover(FlexWidget*, QWidget*)));
    connect(this, SIGNAL(guiderShow(FlexWidget*, QWidget*)), SLOT(on_flexWidget_guiderShow(FlexWidget*, QWidget*)));
    connect(this, SIGNAL(guiderHide(FlexWidget*, QWidget*)), SLOT(on_flexWidget_guiderHide(FlexWidget*, QWidget*)));
    connect(this, SIGNAL(guiderDrop(FlexWidget*, DockWidget*)), SLOT(on_flexWidget_guiderDrop(FlexWidget*, DockWidget*)));
    connect(this, SIGNAL(guiderDrop(FlexWidget*, FlexWidget*)), SLOT(on_flexWidget_guiderDrop(FlexWidget*, FlexWidget*)));
    QPixmap extentsPixmap(":/Resources/extents.png");
    for (int i = 0; i < 3; i++)
    {
        QIcon icon;
        icon.addPixmap(extentsPixmap.copy(i * 16,  0, 16, 16), QIcon::Active, QIcon::On);
        icon.addPixmap(extentsPixmap.copy(i * 16, 16, 16, 16), QIcon::Active, QIcon::Off);
        icon.addPixmap(extentsPixmap.copy(i * 16, 32, 16, 16), QIcon::Normal, QIcon::Off);
        impl->_buttonIcons.append(icon);
    }
    QPixmap buttonsPixmap(":/Resources/buttons.png");
    for (int i = 0; i < 4; i++)
    {
        QIcon icon;
        icon.addPixmap(buttonsPixmap.copy(i * 16,  0, 16, 16), QIcon::Active, QIcon::On);
        icon.addPixmap(buttonsPixmap.copy(i * 16, 16, 16, 16), QIcon::Active, QIcon::Off);
        icon.addPixmap(buttonsPixmap.copy(i * 16, 32, 16, 16), QIcon::Normal, QIcon::Off);
        impl->_buttonIcons.append(icon);
    }
    
    qApp->installNativeEventFilter(impl.data());
}

FlexManager::~FlexManager()
{

}

FlexManager* FlexManager::instance()
{
    static FlexManager manager; return &manager;
}

FlexWidget* FlexManager::createFlexWidget(Flex::ViewMode viewMode, QWidget* parent, Qt::WindowFlags flags, const QString& flexWidgetName)
{
#ifdef _DEBUG
    if (!flexWidgetName.isEmpty() && hasFlexWidget(flexWidgetName))
    {
        qWarning() << tr("FlexManager::createFlexWidget: FlexWidget [%1] alreay exists!").arg(flexWidgetName);
    }
#endif
    FlexWidget* widget = new FlexWidget(viewMode, parent, flags);
    widget->setObjectName(flexWidgetName.isEmpty() ? QUuid::createUuid().toString().toUpper() : flexWidgetName);
    widget->setWindowTitle(flexWidgetName);
    connect(widget, SIGNAL(destroyed(QObject*)), SLOT(on_flexWidget_destroyed(QObject*)));
    connect(widget, SIGNAL(enterMove(QObject*)), SLOT(on_flexWidget_enterMove(QObject*)));
    connect(widget, SIGNAL(leaveMove(QObject*)), SLOT(on_flexWidget_leaveMove(QObject*)));
    connect(widget, SIGNAL(moving(QObject*)), SLOT(on_flexWidget_moving(QObject*)));
    connect(widget, SIGNAL(destroying(FlexWidget*)), SLOT(on_flexWidget_destroying(FlexWidget*)));
    widget->installEventFilter(this);
    impl->_flexWidgets.append(widget);
    emit flexWidgetCreated(widget);
    return widget;
}

DockWidget* FlexManager::createDockWidget(Flex::ViewMode viewMode, QWidget* parent, Qt::WindowFlags flags, const QString& dockWidgetName)
{
#ifdef _DEBUG
    if (!dockWidgetName.isEmpty() && hasDockWidget(dockWidgetName))
    {
        qWarning() << tr("FlexManager::createDockWidget: DockWidget <%1> alreay exists!").arg(dockWidgetName);
    }
#endif
    DockWidget* widget = new DockWidget(viewMode, parent, flags);
    widget->setObjectName(dockWidgetName.isEmpty() ? QUuid::createUuid().toString().toUpper() : dockWidgetName);
    widget->setWindowTitle(dockWidgetName);
    connect(widget, SIGNAL(destroyed(QObject*)), SLOT(on_dockWidget_destroyed(QObject*)));
    connect(widget, SIGNAL(enterMove(QObject*)), SLOT(on_dockWidget_enterMove(QObject*)));
    connect(widget, SIGNAL(leaveMove(QObject*)), SLOT(on_dockWidget_leaveMove(QObject*)));
    connect(widget, SIGNAL(moving(QObject*)), SLOT(on_dockWidget_moving(QObject*)));
    connect(widget, SIGNAL(destroying(DockWidget*)), SLOT(on_dockWidget_destroying(DockWidget*)));
    widget->installEventFilter(this);
    impl->_dockWidgets.append(widget);
    emit dockWidgetCreated(widget);
    return widget;
}

bool FlexManager::hasFlexWidget(const QString& flexWidgetName) const
{
    return std::find_if(impl->_flexWidgets.begin(), impl->_flexWidgets.end(), [&](FlexWidget* flexWidget) { return flexWidget->objectName() == flexWidgetName; }) != impl->_flexWidgets.end();
}

bool FlexManager::hasDockWidget(const QString& dockWidgetName) const
{
    return std::find_if(impl->_dockWidgets.begin(), impl->_dockWidgets.end(), [&](DockWidget* dockWidget) { return dockWidget->objectName() == dockWidgetName; }) != impl->_dockWidgets.end();
}

FlexWidget* FlexManager::flexWidget(const QString& flexWidgetName) const
{
    auto iter = std::find_if(impl->_flexWidgets.begin(), impl->_flexWidgets.end(), [&](FlexWidget* flexWidget) { return flexWidget->objectName() == flexWidgetName; });
    return iter != impl->_flexWidgets.end() ? *iter : nullptr;
}

DockWidget* FlexManager::dockWidget(const QString& dockWidgetName) const
{
    auto iter = std::find_if(impl->_dockWidgets.begin(), impl->_dockWidgets.end(), [&](DockWidget* dockWidget) { return dockWidget->objectName() == dockWidgetName; });
    return iter != impl->_dockWidgets.end() ? *iter : nullptr;
}

int FlexManager::flexWidgetCount() const
{
    return (int)impl->_flexWidgets.size();
}

int FlexManager::dockWidgetCount() const
{
    return (int)impl->_dockWidgets.size();
}

FlexWidget* FlexManager::flexWidgetAt(int index) const
{
    Q_ASSERT(index >= 0 && index < impl->_flexWidgets.size());
    return impl->_flexWidgets[index];
}

DockWidget* FlexManager::dockWidgetAt(int index) const
{
    Q_ASSERT(index >= 0 && index < impl->_dockWidgets.size());
    return impl->_dockWidgets[index];
}

QIcon FlexManager::icon(Flex::Button button)
{
    return impl->_buttonIcons[button];
}

void FlexManager::close()
{
    for (auto iter = impl->_dockWidgets.begin(); iter != impl->_dockWidgets.end(); ++iter)
    {
        if ((*iter)->isWindow())
        {
            (*iter)->deleteLater();
        }
    }
    for (auto iter = impl->_flexWidgets.begin(); iter != impl->_flexWidgets.end(); ++iter)
    {
        if ((*iter)->isWindow())
        {
            (*iter)->deleteLater();
        }
    }
    impl->_dockWidgets.clear();
    impl->_flexWidgets.clear();
}

bool FlexManager::load(const QByteArray& content, const QMap<QString, QWidget*>& parents)
{
    close();

    QJsonObject object = QJsonDocument::fromJson(content).object();

    QJsonArray flexWidgetObjects = object["flexWidgets"].toArray();

    for (int i = 0; i < flexWidgetObjects.size(); ++i)
    {
        QJsonObject flexWidgetObject = flexWidgetObjects[i].toObject();

        Flex::ViewMode viewMode = (Flex::ViewMode)flexWidgetObject["viewMode"].toInt();
        QWidget* parent = parents.value(flexWidgetObject["parent"].toString(), nullptr);
        Qt::WindowFlags flags = (Qt::WindowFlags)flexWidgetObject["windowFlags"].toInt();
        QString flexWidgetName = flexWidgetObject["flexWidgetName"].toString();

        FlexWidget* flexWidget = createFlexWidget(viewMode, parent, Flex::widgetFlags(), flexWidgetName);

        flexWidget->load(flexWidgetObject);
    }

    return true;
}

QByteArray FlexManager::save() const
{
    QJsonObject object;

    QJsonArray flexWidgetObjects;

    for (int i = 0; i < impl->_flexWidgets.size(); ++i)
    {
        FlexWidget* flexWidget = impl->_flexWidgets[i];

        QJsonObject flexWidgetObject;

        flexWidgetObject["viewMode"] = (int)flexWidget->viewMode();
        flexWidgetObject["parent"] = flexWidget->parentWidget() ? flexWidget->parentWidget()->objectName() : "";
        flexWidgetObject["windowFlags"] = (int)Flex::windowFlags(flexWidget->viewMode());
        flexWidgetObject["flexWidgetName"] = flexWidget->objectName();

        flexWidget->save(flexWidgetObject);

        flexWidgetObjects.append(flexWidgetObject);
    }

    object["flexWidgets"] = flexWidgetObjects;

#ifdef _DEBUG
    return QJsonDocument(object).toJson(QJsonDocument::Indented);
#else
    return QJsonDocument(object).toJson(QJsonDocument::Compact);
#endif
}

bool FlexManager::snapshot(DockWidget* dockWidget)
{
    if (!hasDockWidget(dockWidget->objectName()))
    {
        return false;
    }

    FlexWidget* flexWidget = dockWidget->flexWidget();

    if (!flexWidget)
    {
        return false;
    }

    QString dockWidgetPath = dockWidget->identifier();

    SnapshotDict::iterator item;

    if ((item = impl->_snapshotDict.find(dockWidget->objectName())) != impl->_snapshotDict.end())
    {
        if (impl->equalIdentifer(dockWidgetPath, std::get<1>(item.value())))
        {
            return true;
        }
    }

    if (item != impl->_snapshotDict.end())
    {
        if ((--std::get<1>(impl->_snapshotList[std::get<2>(item.value())])) == 0)
        {
            impl->_snapshotList.remove(std::get<2>(item.value()));
        }
    }

    int key = impl->generate();

    auto dockWidgets = flexWidget->findChildren<DockWidget*>();

    int count = 1;

    impl->_snapshotDict[dockWidget->objectName()] = std::make_tuple(flexWidget->objectName(), dockWidgetPath, key);

    foreach (auto tempWidget, dockWidgets)
    {
        if (tempWidget == dockWidget)
        {
            continue;
        }

        dockWidgetPath = tempWidget->identifier();

        if ((item = impl->_snapshotDict.find(tempWidget->objectName())) != impl->_snapshotDict.end())
        {
            if (impl->equalIdentifer(dockWidgetPath, std::get<1>(item.value())))
            {
                continue;
            }
        }

        if (item != impl->_snapshotDict.end())
        {
            if ((--std::get<1>(impl->_snapshotList[std::get<2>(item.value())])) == 0)
            {
                impl->_snapshotList.remove(std::get<2>(item.value()));
            }
        }

        impl->_snapshotDict[tempWidget->objectName()] = std::make_tuple(flexWidget->objectName(), dockWidgetPath, key);

        count++;
    }

    impl->_snapshotList[key] = std::make_tuple(flexWidget->snapshot(), count);

    return true;
}

bool FlexManager::snapshot(FlexWidget* flexWidget)
{
    if (!hasFlexWidget(flexWidget->objectName()))
    {
        return false;
    }

    int key = impl->generate();

    auto dockWidgets = flexWidget->findChildren<DockWidget*>();

    int count = 0;

    SnapshotDict::iterator item;

    foreach(auto tempWidget, dockWidgets)
    {
        QString dockWidgetPath = tempWidget->identifier();

        if ((item = impl->_snapshotDict.find(tempWidget->objectName())) != impl->_snapshotDict.end())
        {
            if (impl->equalIdentifer(dockWidgetPath, std::get<1>(item.value())))
            {
                continue;
            }
        }

        if (item != impl->_snapshotDict.end())
        {
            if ((--std::get<1>(impl->_snapshotList[std::get<2>(item.value())])) == 0)
            {
                impl->_snapshotList.remove(std::get<2>(item.value()));
            }
        }

        impl->_snapshotDict[tempWidget->objectName()] = std::make_tuple(flexWidget->objectName(), dockWidgetPath, key);

        count++;
    }

    impl->_snapshotList[key] = std::make_tuple(flexWidget->snapshot(), count);

    return true;
}

bool FlexManager::restore(const QString& name)
{
    SnapshotDict::iterator item;

    if ((item = impl->_snapshotDict.find(name)) == impl->_snapshotDict.end())
    {
        return false;
    }

    QString flexWidgetName = std::get<0>(item.value());
    QString dockWidgetPath = std::get<1>(item.value());

    int key = std::get<2>(item.value());

    SnapshotNode& node = impl->_snapshotList[key];

    QByteArray& content = std::get<0>(node);

    int& count = std::get<1>(node);

    FlexWidget* flexWidget = nullptr;

    if ((flexWidget = this->flexWidget(flexWidgetName)) == nullptr)
    {
        QJsonObject flexWidgetObject = QJsonDocument::fromJson(content).object();

        Flex::ViewMode viewMode = (Flex::ViewMode)flexWidgetObject["viewMode"].toInt();

        Qt::WindowFlags flags = (Qt::WindowFlags)flexWidgetObject["windowFlags"].toInt();

        flexWidget = createFlexWidget(viewMode, nullptr, flags, flexWidgetName);

        flexWidget->restoreGeometry(QByteArray::fromBase64(flexWidgetObject["geometry"].toString().toLatin1()));
    }

    bool result =  flexWidget->restore(content, dockWidgetPath);

    impl->_snapshotDict.remove(name);

    if (--count == 0)
    {
        impl->_snapshotList.remove(key);
    }

    return result;
}

bool FlexManager::eventFilter(QObject* obj, QEvent* evt)
{
    if (evt->type() == QEvent::Destroy)
    {
        switch (obj->property("class").value<int>())
        {
        case Flex::DockWidget:
            impl->_dockWidgets.removeOne(static_cast<DockWidget*>(obj));
            impl->_dockWidgetDestorying = "";
            break;
        case Flex::FlexWidget:
            impl->_flexWidgets.removeOne(static_cast<FlexWidget*>(obj));
            impl->_flexWidgetDestorying = "";
            break;
        }
    }
#ifndef Q_OS_WIN
    else if (evt->type() == QEvent::WindowActivate)
    {
        FlexWidget* flexWidget;
        if ((flexWidget = qobject_cast<FlexWidget*>(obj)) != nullptr)
        {
            if (impl->_flexWidgets.front() != flexWidget && impl->_flexWidgets.removeOne(flexWidget))
            {
                impl->_flexWidgets.push_front(flexWidget);
            }
            return false;
        }
        DockWidget* dockWidget;
        if ((dockWidget = qobject_cast<DockWidget*>(obj)) != nullptr)
        {
            if (impl->_dockWidgets.front() != dockWidget && impl->_dockWidgets.removeOne(dockWidget))
            {
                impl->_dockWidgets.push_front(dockWidget);
            }
            return false;
        }
    }
#endif
    return false;
}

void FlexManager::on_dockWidget_destroying(DockWidget* widget)
{
    impl->_dockWidgetDestorying = widget->objectName();
    emit dockWidgetDestroying(widget);
}

void FlexManager::on_flexWidget_destroying(FlexWidget* widget)
{
    impl->_flexWidgetDestorying = widget->objectName();
    emit flexWidgetDestroying(widget);
}

void FlexManager::on_dockWidget_destroyed(QObject* widget)
{
    widget->setProperty("class", QVariant::fromValue<int>(Flex::DockWidget));
}

void FlexManager::on_flexWidget_destroyed(QObject* widget)
{
    widget->setProperty("class", QVariant::fromValue<int>(Flex::FlexWidget));
}

void FlexManager::on_flexWidget_guiderShow(FlexWidget* flexWidget, QWidget* widget)
{
    Q_ASSERT(widget != nullptr); 
#ifdef Q_OS_WIN
    SetWindowPos(reinterpret_cast<HWND>(flexWidget->window()->effectiveWinId()), reinterpret_cast<HWND>(widget->effectiveWinId()), 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE);
#else
    flexWidget->window()->raise();
    if (impl->_flexWidgets.size() > 1 && impl->_flexWidgets[1] != flexWidget && impl->_flexWidgets.removeOne(flexWidget))
    {
        impl->_flexWidgets.insert(1, flexWidget);
    }
    widget->raise();
#endif
    flexWidget->showGuider(widget);
}

void FlexManager::on_flexWidget_guiderHide(FlexWidget* flexWidget, QWidget* widget)
{
    Q_ASSERT(widget != nullptr);
    flexWidget->hideGuider(widget);
}

void FlexManager::on_flexWidget_guiderHover(FlexWidget* flexWidget, QWidget* widget)
{
    Q_ASSERT(widget != nullptr);
    flexWidget->hoverGuider(widget);
}

void FlexManager::on_flexWidget_guiderDrop(FlexWidget* flexWidget, DockWidget* widget)
{
    Q_ASSERT(widget != nullptr);
    if (flexWidget->dropGuider(widget))
    {
#ifdef Q_OS_WIN
        SetWindowPos(reinterpret_cast<HWND>(flexWidget->window()->effectiveWinId()), HWND_TOP, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | 1800);
#else
        flexWidget->window()->raise();
#endif
    }
    else
    {
#ifdef Q_OS_WIN
        SetWindowPos(reinterpret_cast<HWND>(widget->effectiveWinId()), HWND_TOP, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE | 1800);
#else
        widget->raise();
#endif
    }
}

void FlexManager::on_flexWidget_guiderDrop(FlexWidget* flexWidget, FlexWidget* widget)
{
    Q_ASSERT(widget != nullptr);
    if (flexWidget->dropGuider(widget))
    {
#ifdef Q_OS_WIN
        SetWindowPos(reinterpret_cast<HWND>(flexWidget->window()->effectiveWinId()), HWND_TOP, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | 1800);
#else
        flexWidget->window()->raise();
#endif
    }
    else
    {
#ifdef Q_OS_WIN
        SetWindowPos(reinterpret_cast<HWND>(widget->effectiveWinId()), HWND_TOP, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE | 1800);
#else
        widget->raise();
#endif
    }
}

void FlexManager::on_flexWidget_enterMove(QObject*)
{
    impl->_ready = true;
}

void FlexManager::on_flexWidget_leaveMove(QObject* object)
{
    if (!impl->_ready)
    {
        return;
    }

    auto widget = static_cast<FlexWidget*>(object);

    auto pos = QCursor::pos();

#ifdef Q_OS_WIN
    auto top = topLevelWindowAt(widget, pos);
#endif

    auto has = false;

    impl->_ready = false;

    for (auto iter = impl->_flexWidgets.begin(); iter != impl->_flexWidgets.end(); ++iter)
    {
        auto flexWidget = *iter;

        if (flexWidget == widget)
        {
            continue;
        }

        if (flexWidget->window()->isMinimized())
        {
            continue;
        }

#ifdef Q_OS_WIN
        if (!has && flexWidget->window()->effectiveWinId() == top && flexWidget->isDockAllowed(widget, flexWidget->mapFromGlobal(pos)))
#else
        if (!has && flexWidget->isDockAllowed(widget, flexWidget->mapFromGlobal(pos)))
#endif
        {
            if (flexWidget->isGuiderExists())
            {
                if (flexWidget->isGuiderVisible())
                {
                    emit guiderDrop(flexWidget, widget);
                }
                else
                {
                    emit guiderHide(flexWidget, widget);
                }
            }
            has = true;
        }
        else
        {
            if (flexWidget->isGuiderExists())
            {
                emit guiderHide(flexWidget, widget); break;
            }
        }
    }
}

void FlexManager::on_flexWidget_moving(QObject* object)
{
    if (!impl->_ready)
    {
        return;
    }

    auto widget = static_cast<FlexWidget*>(object);

    auto pos = QCursor::pos();

#ifdef Q_OS_WIN
    auto top = topLevelWindowAt(widget, pos);
#endif

    auto has = false;

    for (auto iter = impl->_flexWidgets.begin(); iter != impl->_flexWidgets.end(); ++iter)
    {
        auto flexWidget = *iter;

        if (flexWidget == widget)
        {
            continue;
        }

        if (flexWidget->window()->isMinimized())
        {
            continue;
        }

#ifdef Q_OS_WIN
        if (!has && flexWidget->window()->effectiveWinId() == top && flexWidget->isDockAllowed(widget, flexWidget->mapFromGlobal(pos)))
#else
        if (!has && flexWidget->isDockAllowed(widget, flexWidget->mapFromGlobal(pos)))
#endif
        {
            if (flexWidget->isGuiderExists())
            {
                if (flexWidget->isGuiderVisible())
                {
                    emit guiderHover(flexWidget, widget);
                }
            }
            else
            {
                emit guiderShow(flexWidget, widget);
            }

            has = true;
        }
        else
        {
            if (flexWidget->isGuiderExists())
            {
                emit guiderHide(flexWidget, widget);
            }
        }
    }
}

void FlexManager::on_dockWidget_enterMove(QObject*)
{
    impl->_ready = true;
}

void FlexManager::on_dockWidget_leaveMove(QObject* object)
{
    if (!impl->_ready)
    {
        return;
    }

    auto widget = static_cast<DockWidget*>(object);

    auto pos = QCursor::pos();

#ifdef Q_OS_WIN
    auto top = topLevelWindowAt(widget, pos);
#endif

    auto has = false;

    impl->_ready = false;

    for (auto iter = impl->_flexWidgets.begin(); iter != impl->_flexWidgets.end(); ++iter)
    {
        auto flexWidget = *iter;

        if (flexWidget->window()->isMinimized())
        {
            continue;
        }

#ifdef Q_OS_WIN
        if (!has && flexWidget->window()->effectiveWinId() == top && flexWidget->isDockAllowed(widget, flexWidget->mapFromGlobal(pos)))
#else
        if (!has && flexWidget->isDockAllowed(widget, flexWidget->mapFromGlobal(pos)))
#endif
        {
            if (flexWidget->isGuiderExists())
            {
                if (flexWidget->isGuiderVisible())
                {
                    emit guiderDrop(flexWidget, widget);
                }
                else
                {
                    emit guiderHide(flexWidget, widget);
                }
            }
            has = true;
        }
        else
        {
            if (flexWidget->isGuiderExists())
            {
                emit guiderHide(flexWidget, widget); break;
            }
        }
    }
}

void FlexManager::on_dockWidget_moving(QObject* object)
{
    if (!impl->_ready)
    {
        return;
    }

    auto widget = static_cast<DockWidget*>(object);

    auto pos = QCursor::pos();

#ifdef Q_OS_WIN
    auto top = topLevelWindowAt(widget, pos);
#endif

    auto has = false;

    for (auto iter = impl->_flexWidgets.begin(); iter != impl->_flexWidgets.end(); ++iter)
    {
        auto flexWidget = *iter;

        if (flexWidget->window()->isMinimized())
        {
            continue;
        }

#ifdef Q_OS_WIN
        if (!has && flexWidget->window()->effectiveWinId() == top && flexWidget->isDockAllowed(widget, flexWidget->mapFromGlobal(pos)))
#else
        if (!has && flexWidget->isDockAllowed(widget, flexWidget->mapFromGlobal(pos)))
#endif
        {
            if (flexWidget->isGuiderExists())
            {
                if (flexWidget->isGuiderVisible())
                {
                    emit guiderHover(flexWidget, widget);
                }
            }
            else
            {
                emit guiderShow(flexWidget, widget);
            }
            has = true;
        }
        else
        {
            if (flexWidget->isGuiderExists())
            {
                emit guiderHide(flexWidget, widget);
            }
        }
    }
}

void FlexManager::on_app_focusChanged(QWidget* old, QWidget* now)
{
    FlexWidget* flexWidget = nullptr;

    auto oldDockSite = getDockSite(old);
    auto nowDockSite = getDockSite(now);

    if (nowDockSite != nullptr && oldDockSite != nowDockSite)
    {
        if (oldDockSite)
        {
            oldDockSite->setActive(false);
        }

        if (!nowDockSite->isActive())
        {
            if ((flexWidget = nowDockSite->flexWidget()) != nullptr)
            {
                flexWidget->setCurrent(nowDockSite);
            }
            if (nowDockSite->isActive())
            {
                emit dockSiteActivated(nowDockSite);
            }
        }
    }
}

QWidget* Flex::window()
{
    return qobject_cast<QWidget*>(qApp->property("window").value<QObject*>());
}
DockWidget

DockWidget 是一个可停靠的窗口组件,可以嵌入到 FlexWidget 中。它支持多种视图模式和功能,如浮动、停靠、自动隐藏等。
 

#include "QtDockWidget.h"
#include "QtDockSite.h"
#include "QtDockSide.h"
#include "QtFlexWidget.h"
#include "QtFlexHelper.h"
#include <QtCore/QJsonObject>
#include <QtCore/QJsonArray>
#include <QtCore/QJsonDocument>
#include <QtGui/QMoveEvent>
#include <QtGui/QPainter>
#include <QtWidgets/QApplication>
#include <QtWidgets/QStylePainter>
#include <QtWidgets/QStyleOption>
#include <QtWidgets/QToolButton>
#include <QtWidgets/QHBoxLayout>
#include <QtWidgets/QDesktopWidget>

#ifdef Q_OS_WIN
#include <qt_windows.h>
#endif

class DockWidgetImpl
{
public:
    DockWidgetImpl() : _active(false), _helper(nullptr)
    {
        _background.setRgbF(1.0f * qrand() / RAND_MAX, 1.0f * qrand() / RAND_MAX, 1.0f * qrand() / RAND_MAX);
        _dockFeatures = Flex::AllowDockAsNorthTabPage | Flex::AllowDockAsSouthTabPage;
        _siteFeatures = Flex::AllowDockAsNorthTabPage | Flex::AllowDockAsSouthTabPage;
    }

public:
    void update(DockWidget* self);

public:
    bool isTitleBarVisible(DockWidget* self, QRect* rect = nullptr) const;

public:
    bool _active;
    QColor _background;
    Flex::ViewMode _viewMode;
    Flex::Features _dockFeatures;
    Flex::Features _siteFeatures;
    QWidget* _widget;
    QString _flexWidgetName;
    QVBoxLayout* _layout;
    FlexHelper* _helper;
    int _titleBarHeight;
};

void DockWidgetImpl::update(DockWidget* self)
{
    auto palette = self->palette();

    switch (_viewMode)
    {
    case Flex::ToolView:
        palette.setColor(QPalette::Active, self->backgroundRole(), QColor("#FFF29D"));
        palette.setColor(QPalette::Inactive, self->backgroundRole(), QColor("#4D6082"));
        palette.setColor(QPalette::Active, QPalette::Highlight, QColor("#FFF29D"));
        palette.setColor(QPalette::Inactive, QPalette::Highlight, QColor("#4D6082"));
        palette.setColor(QPalette::Active, self->foregroundRole(), QColor("#000000"));
        palette.setColor(QPalette::Inactive, self->foregroundRole(), QColor("#FFFFFF"));
        self->setDockFeatures(Flex::AllowDockAsNorthTabPage | Flex::AllowDockAsSouthTabPage);
        self->setSiteFeatures(Flex::AllowDockAsSouthTabPage);
        break;
    case Flex::FileView:
        palette.setColor(QPalette::All, self->backgroundRole(), QColor("#293955"));
        palette.setColor(QPalette::Active, QPalette::Highlight, QColor("#293955"));
        palette.setColor(QPalette::Inactive, QPalette::Highlight, QColor("#293955"));
        palette.setColor(QPalette::Active, self->foregroundRole(), QColor("#FFFFFF"));
        palette.setColor(QPalette::Inactive, self->foregroundRole(), QColor("#7F8899"));
        self->setDockFeatures(Flex::AllowDockAsNorthTabPage);
        self->setSiteFeatures(Flex::AllowDockAsNorthTabPage | Flex::AllowDockAsSouthTabPage);
        break;
    default:
        Q_ASSERT(false);
        break;
    }

    self->setPalette(palette);

    if (!self->isWindow())
    {
        if (_helper)
        {
            _helper->deleteLater(); _helper = nullptr;
        }

        self->setContentsMargins(0, 0, 0, 0);
    }
    else
    {
        if (!_helper)
        {
            _helper = new FlexHelper(self);
            self->connect(_helper, SIGNAL(clicked(Flex::Button, bool*)), SLOT(on_titleBar_buttonClicked(Flex::Button, bool*)));
        }

        switch (_viewMode)
        {
        case Flex::FileView:
            _helper->button(Flex::Minimize)->show();
            _helper->button(Flex::DockPull)->hide();
            _helper->button(Flex::AutoHide)->hide();
            _titleBarHeight = self->style()->pixelMetric(QStyle::PM_TitleBarHeight, nullptr, self) - 4;
            break;
        case Flex::ToolView:
            _helper->button(Flex::Minimize)->hide();
            _helper->button(Flex::DockPull)->show();
            _helper->button(Flex::AutoHide)->hide();
            _titleBarHeight = self->style()->pixelMetric(QStyle::PM_TitleBarHeight, nullptr, self) - 4;
            break;
        default:
            break;
        }

        _helper->setWindowInfo(_titleBarHeight, Flex::windowFlags(_viewMode));

        self->setContentsMargins(0, _titleBarHeight, 0, 0);
    }
}

bool DockWidgetImpl::isTitleBarVisible(DockWidget* self, QRect* rect) const
{
    if (self->isFloating())
    {
        if (rect) *rect = QRect(0, 0, self->width(), _titleBarHeight); return true;
    }
    else
    {
        if (rect) *rect = QRect(); return false;
    }
}

DockWidget::DockWidget(Flex::ViewMode viewMode, QWidget* parent, Qt::WindowFlags flags) : QWidget(parent, flags), impl(new DockWidgetImpl)
{
    setAttribute(Qt::WA_DeleteOnClose);
    setFocusPolicy(Qt::StrongFocus);
    setProperty("Flex", true);

    impl->_widget = new QWidget(this);

    impl->_viewMode = viewMode;

    impl->update(this);

    impl->_layout = new QVBoxLayout(this);
    impl->_layout->setContentsMargins(0, 0, 0, 0);
    impl->_layout->setSpacing(0);

    impl->_layout->addWidget(impl->_widget);
}

DockWidget::~DockWidget()
{
    emit destroying(this);
}

bool DockWidget::event(QEvent* evt)
{
    if (evt->type() == QEvent::WinIdChange)
    {
        if (!internalWinId())
        {
            impl->update(this);
        }
    }
    else if (evt->type() == Flex::Update)
    {
        impl->_flexWidgetName = flexWidgetName();
    }
    return QWidget::event(evt);
}

bool DockWidget::nativeEvent(const QByteArray& eventType, void *message, long *result)
{
    if (impl->_helper && impl->_helper->nativeEvent(eventType, message, result))
    {
        return true;
    }
    else
    {
        return QWidget::nativeEvent(eventType, message, result);
    }
}

void DockWidget::paintEvent(QPaintEvent*)
{
    QStylePainter painter(this);

    painter.fillRect(rect().adjusted(5, 5, -5, -5), impl->_background);

    QRect titleBarRect;

    if (impl->isTitleBarVisible(this, &titleBarRect))
    {
        if (impl->_helper)
        {
            impl->_helper->buttons()->show();
            impl->_helper->extents()->show();
        }

        QStyleOptionTitleBar titleOption;
        titleOption.init(this);
        titleOption.rect = titleBarRect;
        titleOption.text = windowTitle();
        titleOption.icon = windowIcon();
        titleOption.subControls = QStyle::SC_TitleBarLabel;
        titleOption.titleBarState = windowState();
        titleOption.titleBarFlags = Flex::windowFlags(impl->_viewMode);

        bool isActive = (titleOption.state & QStyle::State_Active) != 0;

        painter.fillRect(titleBarRect, titleOption.palette.color(QPalette::Highlight));

        bool hasIcon = titleOption.titleBarFlags & Qt::WindowSystemMenuHint;

        QRect lr = style()->subControlRect(QStyle::CC_TitleBar, &titleOption, QStyle::SC_TitleBarLabel, this);

        if (hasIcon)
        {
            titleOption.icon.paint(&painter, 5, (impl->_titleBarHeight - 16) / 2, 16, 16, Qt::AlignCenter, QIcon::Active, isActive ? QIcon::On : QIcon::Off);
        }

        painter.drawText(8 + (hasIcon ? 18 : 0), (impl->_titleBarHeight - lr.height()) / 2, lr.width() - 2, lr.height(), Qt::AlignLeft | Qt::AlignVCenter | Qt::TextSingleLine, titleOption.text);

        QStyleOptionFrame frameOption;
        frameOption.initFrom(this);
        painter.drawPrimitive(QStyle::PE_Frame, frameOption);
    }
    else
    {
        if (impl->_helper)
        {
            impl->_helper->buttons()->hide();
            impl->_helper->extents()->hide();
        }
    }
}

void DockWidget::closeEvent(QCloseEvent*)
{
    
}

QSize DockWidget::sizeHint() const
{
    return isFloating() ? QSize(640, 480) : QWidget::sizeHint();
}

QSize DockWidget::minimumSizeHint() const
{
    return isFloating() ? QSize(80, 40) : QWidget::minimumSizeHint();
}

Flex::ViewMode DockWidget::viewMode() const
{
    return impl->_viewMode;
}

void DockWidget::setViewMode(Flex::ViewMode viewMode)
{
    if (impl->_viewMode != viewMode)
    {
        impl->_viewMode = viewMode;
        impl->update(this);
    }
}

Flex::Features DockWidget::dockFeatures() const
{
    return impl->_dockFeatures;
}

void DockWidget::setDockFeatures(Flex::Features features)
{
    impl->_dockFeatures = features;
}

Flex::Features DockWidget::siteFeatures() const
{
    return impl->_siteFeatures;
}

void DockWidget::setSiteFeatures(Flex::Features features)
{
    impl->_siteFeatures = features;
}

DockSite* DockWidget::dockSite() const
{
    QWidget* tempWidget = parentWidget();
    while (tempWidget && !qobject_cast<DockSite*>(tempWidget))
    {
        tempWidget = tempWidget->parentWidget();
    }
    return qobject_cast<DockSite*>(tempWidget);
}

DockSide* DockWidget::dockSide() const
{
    FlexWidget* flexWidget = this->flexWidget();

    if (flexWidget == nullptr)
    {
        return nullptr;
    }

    DockSite* dockSite = this->dockSite();

    if (dockSite == nullptr)
    {
        return nullptr;
    }

    return dockSite->dockSide();
}

FlexWidget* DockWidget::flexWidget() const
{
    QWidget* tempWidget = parentWidget();
    while (tempWidget && !qobject_cast<FlexWidget*>(tempWidget))
    {
        tempWidget = tempWidget->parentWidget();
    }
    return qobject_cast<FlexWidget*>(tempWidget);
}

QString DockWidget::flexWidgetName() const
{
    if (impl->_flexWidgetName.isEmpty())
    {
        return [](FlexWidget* widget) { return widget ? widget->objectName() : ""; }(flexWidget());
    }
    else
    {
        return impl->_flexWidgetName;
    }
}

QWidget* DockWidget::widget() const
{
    return impl->_widget;
}

void DockWidget::attachWidget(QWidget* widget)
{
    if (impl->_widget != widget)
    {
        if (impl->_widget)
        {
            delete impl->_layout->takeAt(0);
        }
        impl->_layout->addWidget(widget);
        impl->_widget = widget;
    }
    else
    {
        Q_ASSERT(false);
    }
}

void DockWidget::detachWidget(QWidget* widget)
{
    if (impl->_widget == widget)
    {
        if (impl->_widget)
        {
            delete impl->_layout->takeAt(0);
        }
        impl->_widget->setParent(nullptr);
        impl->_widget = nullptr;
    }
    else
    {
        Q_ASSERT(false);
    }
}

void DockWidget::setWidget(QWidget* widget)
{
    if (impl->_widget != widget)
    {
        if (impl->_widget)
        {
            delete impl->_layout->takeAt(0);
        }

        impl->_widget->deleteLater();

        impl->_layout->addWidget(widget);

        impl->_widget = widget;
    }
}

void DockWidget::activate()
{
    QWidget* window = this->window();

    if (!isFloating() && !window->isActiveWindow())
    {
        window->activateWindow();
    }

    DockSite* dockSite = this->dockSite();

    if (dockSite)
    {
        dockSite->setCurrentWidget(this);
    }
    else
    {
        setFocus();
    }

    DockSide* dockSide = this->dockSide();

    if (dockSide)
    {
        dockSide->makeCurrent(dockSite);
    }
}

bool DockWidget::isFloating() const
{
    return isTopLevel();
}

bool DockWidget::isActive() const
{
    return isActiveWindow();
}

bool DockWidget::load(const QJsonObject& object)
{
    setWindowTitle(object["dockWidgetName"].toString());
    setViewMode((Flex::ViewMode)object["viewMode"].toInt());
    setDockFeatures((Flex::Features)object["dockFeatures"].toInt());
    setSiteFeatures((Flex::Features)object["siteFeatures"].toInt());
    return true;
}

bool DockWidget::save(QJsonObject& object) const
{
    object["dockWidgetName"] = windowTitle();
    object["viewMode"] = (int)viewMode();
    object["dockFeatures"] = (int)dockFeatures();
    object["siteFeatures"] = (int)siteFeatures();
    return true;
}

QString DockWidget::identifier()
{
    DockSite* dockSite = this->dockSite();

    if (dockSite == nullptr)
    {
        return objectName();
    }
    else
    {
        return QString("%1,%2,%3").arg(dockSite->identifier()).arg(dockSite->indexOf(this)).arg(objectName());
    }
}

void DockWidget::on_titleBar_buttonClicked(Flex::Button button, bool*)
{
    switch (button)
    {
    case Flex::AutoHide:
        break;
    case Flex::DockShow:
        break;
    default:
        break;
    }
}
DockSite

DockSite 是 DockWidget 的容器,可以包含多个 DockWidget。它支持标签页切换、拖动、关闭等操作。

DockSide

DockSide 是 DockSite 的容器,可以包含多个 DockSite。它支持拖动和调整大小等操作。

FlexStyle

FlexStyle 是一个自定义的 Qt 样式类,用于定义窗口的外观和行为。

FlexHelper

FlexHelper 是一个辅助类,用于处理窗口的标题栏按钮、边框等操作。


网站公告

今日签到

点亮在社区的每一天
去签到