356 lines
11 KiB
C++
356 lines
11 KiB
C++
#ifndef ADAPTIXCLIENT_ABSTRACTDOCK_H
|
|
#define ADAPTIXCLIENT_ABSTRACTDOCK_H
|
|
|
|
#include <kddockwidgets/qtwidgets/views/DockWidget.h>
|
|
#include <kddockwidgets/qtwidgets/views/MainWindow.h>
|
|
#include <kddockwidgets/qtwidgets/views/TabBar.h>
|
|
#include <kddockwidgets/core/DockWidget.h>
|
|
#include <kddockwidgets/core/Group.h>
|
|
#include <kddockwidgets/core/Stack.h>
|
|
#include <kddockwidgets/core/TabBar.h>
|
|
#include <kddockwidgets/core/Controller.h>
|
|
#include <kddockwidgets/qtcommon/View.h>
|
|
#include <QTableView>
|
|
#include <QTreeWidget>
|
|
#include <QTreeView>
|
|
#include <QTextEdit>
|
|
#include <QPlainTextEdit>
|
|
#include <QAbstractItemModel>
|
|
#include <QTimer>
|
|
#include <QScrollBar>
|
|
#include <QSet>
|
|
#include <MainAdaptix.h>
|
|
#include <Client/Settings.h>
|
|
#include <typeinfo>
|
|
#include <cstdlib>
|
|
#ifdef __GNUC__
|
|
#include <cxxabi.h>
|
|
#endif
|
|
|
|
/**
|
|
* @brief Base class for all dock widgets with tab blinking support on updates.
|
|
*
|
|
* AUTOMATIC BLINK:
|
|
* - Enabled by default for QTableView, QTreeWidget, QTreeView, QTextEdit, QPlainTextEdit
|
|
* - Triggers automatically when rows are inserted into model or text changes
|
|
* - 100ms debounce prevents too frequent triggers
|
|
* - blink clears when user scrolls to see new content (not just on tab switch)
|
|
*
|
|
* DISABLE FOR SPECIFIC WIDGET:
|
|
* @code
|
|
* // In widget constructor:
|
|
* setAutoBlinkEnabled(false); // Completely disable auto-blinks
|
|
* @endcode
|
|
*
|
|
* TEMPORARY DISABLE (e.g., during bulk data loading):
|
|
* @code
|
|
* void MyWidget::loadBulkData() {
|
|
* AutoBlinkGuard guard(this); // Disables blinks
|
|
* // ... bulk loading ...
|
|
* } // Blinks are re-enabled automatically
|
|
* @endcode
|
|
*
|
|
* MANUAL CALL (for custom widgets):
|
|
* @code
|
|
* blinkNewContent(); // Highlights the tab if inactive
|
|
* @endcode
|
|
*/
|
|
|
|
class DockTab : public QWidget {
|
|
Q_OBJECT
|
|
protected:
|
|
KDDockWidgets::QtWidgets::DockWidget* dockWidget;
|
|
mutable QString m_cachedClassName;
|
|
bool m_autoBlinkEnabled = true;
|
|
QTimer* m_debounceTimer = nullptr;
|
|
|
|
QSet<int> m_newTableRows;
|
|
int m_newTextPosition = -1;
|
|
QSet<QAbstractItemView*> m_trackedViews;
|
|
QSet<QAbstractScrollArea*> m_trackedTextEdits;
|
|
|
|
QString getClassName() const {
|
|
if (!m_cachedClassName.isEmpty())
|
|
return m_cachedClassName;
|
|
#ifdef __GNUC__
|
|
int status;
|
|
char* demangled = abi::__cxa_demangle(typeid(*this).name(), nullptr, nullptr, &status);
|
|
m_cachedClassName = (status == 0 && demangled) ? QString(demangled) : QString(typeid(*this).name());
|
|
free(demangled);
|
|
#else
|
|
m_cachedClassName = QString(typeid(*this).name());
|
|
#endif
|
|
return m_cachedClassName;
|
|
}
|
|
|
|
public:
|
|
DockTab(const QString &tabName, const QString &projectName, const QString &icon = "") {
|
|
dockWidget = new KDDockWidgets::QtWidgets::DockWidget(tabName + ":Dock-" + projectName, KDDockWidgets::DockWidgetOption_None, KDDockWidgets::LayoutSaverOption::None);
|
|
dockWidget->setTitle(tabName);
|
|
if (!icon.isEmpty())
|
|
dockWidget->setIcon(QIcon(icon), KDDockWidgets::IconPlace::TabBar);
|
|
|
|
connect(dockWidget, &KDDockWidgets::QtWidgets::DockWidget::isCurrentTabChanged, this, &DockTab::onCurrentTabChanged);
|
|
|
|
QTimer::singleShot(0, this, &DockTab::setupAutoBlink);
|
|
};
|
|
|
|
~DockTab() override {
|
|
if (dockWidget) {
|
|
dockWidget->setWidget(nullptr);
|
|
delete dockWidget;
|
|
dockWidget = nullptr;
|
|
}
|
|
};
|
|
|
|
KDDockWidgets::QtWidgets::DockWidget* dock() { return this->dockWidget; };
|
|
|
|
void setAutoBlinkEnabled(bool enabled) { m_autoBlinkEnabled = enabled; }
|
|
|
|
bool isAutoBlinkEnabled() const { return m_autoBlinkEnabled; }
|
|
|
|
void blinkNewContent() {
|
|
if (GlobalClient && GlobalClient->settings) {
|
|
auto& data = GlobalClient->settings->data;
|
|
if (!data.TabBlinkEnabled)
|
|
return;
|
|
|
|
QString className = getClassName();
|
|
if (data.BlinkWidgets.contains(className) && !data.BlinkWidgets[className])
|
|
return;
|
|
}
|
|
|
|
auto* coreDw = dockWidget->dockWidget();
|
|
if (!coreDw) return;
|
|
|
|
if (!coreDw->isCurrentTab()) {
|
|
if (auto tabBar = getTabBar()) {
|
|
int index = getTabIndex();
|
|
if (index >= 0) {
|
|
tabBar->setTabHighlighted(index, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
protected:
|
|
KDDockWidgets::QtWidgets::TabBar* getTabBar() const {
|
|
auto* group = dockWidget->group();
|
|
if (!group) return nullptr;
|
|
|
|
auto* stack = group->stack();
|
|
if (!stack) return nullptr;
|
|
|
|
auto* coreTabBar = stack->tabBar();
|
|
if (!coreTabBar) return nullptr;
|
|
|
|
return static_cast<KDDockWidgets::QtWidgets::TabBar*>(
|
|
KDDockWidgets::QtCommon::View_qt::asQWidget(static_cast<KDDockWidgets::Core::Controller*>(coreTabBar))
|
|
);
|
|
}
|
|
|
|
int getTabIndex() const {
|
|
auto* coreDw = dockWidget->dockWidget();
|
|
if (!coreDw) return -1;
|
|
|
|
auto* group = dockWidget->group();
|
|
if (!group) return -1;
|
|
|
|
auto* stack = group->stack();
|
|
if (!stack) return -1;
|
|
|
|
return stack->tabBar()->indexOfDockWidget(coreDw);
|
|
}
|
|
|
|
void clearHighlight() {
|
|
if (auto tabBar = getTabBar()) {
|
|
int index = getTabIndex();
|
|
if (index >= 0 && tabBar->isTabHighlighted(index)) {
|
|
tabBar->setTabHighlighted(index, false);
|
|
}
|
|
}
|
|
m_newTableRows.clear();
|
|
m_newTextPosition = -1;
|
|
}
|
|
|
|
bool hasNewContent() const {
|
|
return !m_newTableRows.isEmpty() || m_newTextPosition >= 0;
|
|
}
|
|
|
|
private Q_SLOTS:
|
|
void onCurrentTabChanged(bool isCurrent) {
|
|
if (isCurrent) {
|
|
clearHighlight();
|
|
}
|
|
}
|
|
|
|
void setupAutoBlink() {
|
|
connectChildWidgets(this);
|
|
}
|
|
|
|
void onTableRowsInserted(const QModelIndex &parent, int first, int last) {
|
|
Q_UNUSED(parent)
|
|
if (!m_autoBlinkEnabled) return;
|
|
|
|
for (int i = first; i <= last; ++i) {
|
|
m_newTableRows.insert(i);
|
|
}
|
|
triggerBlink();
|
|
}
|
|
|
|
void onTextChanged() {
|
|
if (!m_autoBlinkEnabled) return;
|
|
|
|
if (auto* textEdit = qobject_cast<QTextEdit*>(sender())) {
|
|
m_newTextPosition = textEdit->document()->blockCount() - 1;
|
|
} else if (auto* plainTextEdit = qobject_cast<QPlainTextEdit*>(sender())) {
|
|
m_newTextPosition = plainTextEdit->document()->blockCount() - 1;
|
|
}
|
|
triggerBlink();
|
|
}
|
|
|
|
void triggerBlink() {
|
|
if (!m_debounceTimer) {
|
|
m_debounceTimer = new QTimer(this);
|
|
m_debounceTimer->setSingleShot(true);
|
|
connect(m_debounceTimer, &QTimer::timeout, this, [this]() {
|
|
blinkNewContent();
|
|
});
|
|
}
|
|
|
|
if (!m_debounceTimer->isActive()) {
|
|
m_debounceTimer->start(100);
|
|
}
|
|
}
|
|
|
|
void onScroll() {
|
|
checkNewContentVisibility();
|
|
}
|
|
|
|
void checkNewContentVisibility() {
|
|
auto* coreDw = dockWidget->dockWidget();
|
|
if (!coreDw || !coreDw->isCurrentTab())
|
|
return;
|
|
|
|
if (!hasNewContent()) {
|
|
clearHighlight();
|
|
return;
|
|
}
|
|
|
|
bool hasVisibleViews = false;
|
|
|
|
if (!m_newTableRows.isEmpty()) {
|
|
for (auto* view : m_trackedViews) {
|
|
if (!view->isVisible()) continue;
|
|
hasVisibleViews = true;
|
|
|
|
QSet<int> stillHidden;
|
|
for (int row : m_newTableRows) {
|
|
QModelIndex idx = view->model()->index(row, 0);
|
|
QRect rect = view->visualRect(idx);
|
|
if (!view->viewport()->rect().intersects(rect)) {
|
|
stillHidden.insert(row);
|
|
}
|
|
}
|
|
m_newTableRows = stillHidden;
|
|
}
|
|
|
|
if (!hasVisibleViews && !m_trackedViews.isEmpty()) {
|
|
m_newTableRows.clear();
|
|
}
|
|
}
|
|
|
|
if (m_newTextPosition >= 0) {
|
|
bool hasVisibleEdits = false;
|
|
for (auto* scrollArea : m_trackedTextEdits) {
|
|
if (!scrollArea->isVisible()) continue;
|
|
hasVisibleEdits = true;
|
|
|
|
QScrollBar* vbar = scrollArea->verticalScrollBar();
|
|
if (vbar && vbar->value() >= vbar->maximum() - 10) {
|
|
m_newTextPosition = -1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!hasVisibleEdits && !m_trackedTextEdits.isEmpty()) {
|
|
m_newTextPosition = -1;
|
|
}
|
|
}
|
|
|
|
if (!hasNewContent()) {
|
|
clearHighlight();
|
|
}
|
|
}
|
|
|
|
private:
|
|
template<typename T>
|
|
void connectItemView(T* view) {
|
|
if (!view) return;
|
|
m_trackedViews.insert(view);
|
|
auto* model = view->model();
|
|
if (model && model->metaObject()) {
|
|
connect(model, &QAbstractItemModel::rowsInserted,
|
|
this, &DockTab::onTableRowsInserted, Qt::UniqueConnection);
|
|
}
|
|
auto* vbar = view->verticalScrollBar();
|
|
if (vbar && vbar->metaObject()) {
|
|
connect(vbar, &QScrollBar::valueChanged,
|
|
this, &DockTab::onScroll, Qt::UniqueConnection);
|
|
}
|
|
}
|
|
|
|
template<typename T, typename Signal>
|
|
void connectTextEdit(T* edit, Signal signal) {
|
|
if (!edit || !edit->metaObject()) return;
|
|
m_trackedTextEdits.insert(edit);
|
|
connect(edit, signal, this, &DockTab::onTextChanged, Qt::UniqueConnection);
|
|
auto* vbar = edit->verticalScrollBar();
|
|
if (vbar && vbar->metaObject()) {
|
|
connect(vbar, &QScrollBar::valueChanged,
|
|
this, &DockTab::onScroll, Qt::UniqueConnection);
|
|
}
|
|
}
|
|
|
|
void connectChildWidgets(const QWidget* parent) {
|
|
for (auto* w : parent->findChildren<QTableView*>())
|
|
connectItemView(w);
|
|
|
|
for (auto* w : parent->findChildren<QTreeWidget*>())
|
|
connectItemView(w);
|
|
|
|
for (auto* w : parent->findChildren<QTreeView*>())
|
|
if (!qobject_cast<QTreeWidget*>(w)) connectItemView(w);
|
|
|
|
for (auto* w : parent->findChildren<QTextEdit*>())
|
|
connectTextEdit(w, &QTextEdit::textChanged);
|
|
|
|
for (auto* w : parent->findChildren<QPlainTextEdit*>())
|
|
connectTextEdit(w, &QPlainTextEdit::textChanged);
|
|
}
|
|
};
|
|
|
|
|
|
|
|
/// RAII class for temporarily disabling auto-blinks
|
|
/// Usage:
|
|
/// {
|
|
/// AutoBlinkGuard guard(this); // disables blinks
|
|
/// // ... data loading ...
|
|
/// } // blinks are re-enabled automatically
|
|
class AutoBlinkGuard {
|
|
public:
|
|
explicit AutoBlinkGuard(DockTab* tab) : m_tab(tab), m_wasEnabled(tab->isAutoBlinkEnabled()) {
|
|
m_tab->setAutoBlinkEnabled(false);
|
|
}
|
|
~AutoBlinkGuard() {
|
|
m_tab->setAutoBlinkEnabled(m_wasEnabled);
|
|
}
|
|
AutoBlinkGuard(const AutoBlinkGuard&) = delete;
|
|
AutoBlinkGuard& operator=(const AutoBlinkGuard&) = delete;
|
|
private:
|
|
DockTab* m_tab;
|
|
bool m_wasEnabled;
|
|
};
|
|
|
|
#endif //ADAPTIXCLIENT_ABSTRACTDOCK_H
|