Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 147 additions & 1 deletion ui/ttdcallswidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ limitations under the License.
#include <QClipboard>
#include <QFrame>
#include <QTimer>
#include <QTabBar>
#include <QKeyEvent>
#include <QMouseEvent>
#include <QStyleOptionTab>
#include <map>

#include "moc_ttdcallswidget.cpp"
Expand Down Expand Up @@ -681,7 +685,11 @@ TTDCallsWidget::TTDCallsWidget(QWidget* parent, BinaryViewRef data) : QWidget(pa
setupUI();
}

TTDCallsWidget::~TTDCallsWidget() {}
TTDCallsWidget::~TTDCallsWidget()
{
// If a rename is in flight, drop the app-wide filter now rather than leave it pointing at a dead object
finishTabRename(false);
}

void TTDCallsWidget::setupUI()
{
Expand Down Expand Up @@ -709,6 +717,7 @@ void TTDCallsWidget::setupUI()
// Connect signals
connect(m_newTabButton, &QToolButton::clicked, this, &TTDCallsWidget::createNewTab);
connect(m_tabWidget, &QTabWidget::tabCloseRequested, this, &TTDCallsWidget::closeTab);
connect(m_tabWidget, &QTabWidget::tabBarDoubleClicked, this, &TTDCallsWidget::renameTab);
}

void TTDCallsWidget::createNewTab()
Expand Down Expand Up @@ -738,6 +747,9 @@ void TTDCallsWidget::createNewTab()

void TTDCallsWidget::closeTab(int index)
{
// Closing any tab can shift the indices of every tab after it, so just end the rename outright
finishTabRename(false);

if (m_tabWidget->count() > 1)
{
QWidget* widget = m_tabWidget->widget(index);
Expand All @@ -746,6 +758,140 @@ void TTDCallsWidget::closeTab(int index)
}
}

void TTDCallsWidget::renameTab(int index)
{
if (index < 0)
// double-click landed on the empty strip, not a tab
return;

// Cancel any in-progress rename before starting another one
finishTabRename(false);

QTabBar* tabBar = m_tabWidget->tabBar();

m_tabRenameOriginalText = tabBar->tabText(index);
m_tabRenameWidestText = m_tabRenameOriginalText;
m_tabRenameIndex = index;

// Anchor left/top/height once - re-deriving them live let the editor drift left as the tab bar reflowed
QStyleOptionTab option;
option.initFrom(tabBar);
option.rect = tabBar->tabRect(index);
option.text = m_tabRenameOriginalText;
if (index == tabBar->currentIndex())
option.state |= QStyle::State_Selected;
if (QWidget* rightButton = tabBar->tabButton(index, QTabBar::RightSide))
option.rightButtonSize = rightButton->size();
if (QWidget* leftButton = tabBar->tabButton(index, QTabBar::LeftSide))
option.leftButtonSize = leftButton->size();

m_tabRenameBaseRect = tabBar->style()->subElementRect(QStyle::SE_TabBarTabText, &option, tabBar);

QLineEdit* editor = new QLineEdit(m_tabRenameOriginalText, tabBar);
editor->setFrame(false);
editor->setContentsMargins(0, 0, 0, 0);
editor->setTextMargins(0, 0, 0, 0);

m_tabRenameEditor = editor;
updateTabRenameGeometry();

editor->show();
editor->setFocus();
editor->selectAll();

connect(editor, &QLineEdit::textChanged, this, &TTDCallsWidget::onTabRenameTextChanged);
connect(editor, &QLineEdit::editingFinished, this, [this]() { finishTabRename(true); });

// App-wide filter: catches Escape/focus-out on the editor and clicks anywhere else
QApplication::instance()->installEventFilter(this);
}

void TTDCallsWidget::onTabRenameTextChanged(const QString& text)
{
if (m_tabRenameIndex < 0)
return;

QTabBar* tabBar = m_tabWidget->tabBar();

// Floor at the widest text reached; collapsing straight to empty caused a render glitch
QFontMetrics metrics(tabBar->font());
if (metrics.horizontalAdvance(text) > metrics.horizontalAdvance(m_tabRenameWidestText))
m_tabRenameWidestText = text;

bool shrinking = metrics.horizontalAdvance(text) < metrics.horizontalAdvance(m_tabRenameWidestText);
tabBar->setTabText(m_tabRenameIndex, shrinking ? m_tabRenameWidestText : text);
updateTabRenameGeometry();
}

void TTDCallsWidget::updateTabRenameGeometry()
{
if (!m_tabRenameEditor)
return;

// Floored to match the real tab's width, or clearing the text would shrink the editor while the tab stays wide
QFontMetrics metrics(m_tabRenameEditor->font());
int textWidth = qMax(metrics.horizontalAdvance(m_tabRenameEditor->text()), metrics.horizontalAdvance(m_tabRenameWidestText));
int width = qMax(m_tabRenameBaseRect.width(), textWidth + 12);
int right = m_tabRenameBaseRect.left() + width;

// Clamp short of the close button's live position so the editor can never grow over it
if (QWidget* closeButton = m_tabWidget->tabBar()->tabButton(m_tabRenameIndex, QTabBar::RightSide))
right = qMin(right, closeButton->geometry().left() - 2);

QRect rect(m_tabRenameBaseRect.left(), m_tabRenameBaseRect.top(), qMax(0, right - m_tabRenameBaseRect.left()), m_tabRenameBaseRect.height());
m_tabRenameEditor->setGeometry(rect);
}

void TTDCallsWidget::finishTabRename(bool commit)
{
if (!m_tabRenameEditor)
return;

QLineEdit* editor = m_tabRenameEditor;
int index = m_tabRenameIndex;
QString originalText = m_tabRenameOriginalText;
m_tabRenameEditor = nullptr;
m_tabRenameIndex = -1;

QString finalText = commit ? editor->text().trimmed() : originalText;
if (finalText.isEmpty())
finalText = originalText;

m_tabWidget->setTabText(index, finalText);
editor->deleteLater();

QApplication::instance()->removeEventFilter(this);
}

bool TTDCallsWidget::eventFilter(QObject* watched, QEvent* event)
{
if (watched == m_tabRenameEditor)
{
if (event->type() == QEvent::KeyPress)
{
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
if (keyEvent->key() == Qt::Key_Escape)
{
finishTabRename(false);
return true;
}
}
else if (event->type() == QEvent::FocusOut)
{
finishTabRename(true);
}
}
else if (m_tabRenameEditor && event->type() == QEvent::MouseButtonPress)
{
QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
QRect editorRect(m_tabRenameEditor->mapToGlobal(QPoint(0, 0)), m_tabRenameEditor->size());
if (!editorRect.contains(mouseEvent->globalPosition().toPoint()))
finishTabRename(true);
}

return QWidget::eventFilter(watched, event);
}

TTDCallsQueryWidget* TTDCallsWidget::getCurrentOrNewQueryWidget()
{
// Get current tab widget
Expand Down
12 changes: 12 additions & 0 deletions ui/ttdcallswidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,16 @@ class TTDCallsWidget : public QWidget
DbgRef<DebuggerController> m_controller;
QTabWidget* m_tabWidget;
QToolButton* m_newTabButton;
QLineEdit* m_tabRenameEditor = nullptr;
int m_tabRenameIndex = -1;
QString m_tabRenameOriginalText;
QString m_tabRenameWidestText;
QRect m_tabRenameBaseRect;

void setupUI();
void finishTabRename(bool commit);
void updateTabRenameGeometry();
void onTabRenameTextChanged(const QString& text);

public:
TTDCallsWidget(QWidget* parent, BinaryViewRef data);
Expand All @@ -163,9 +171,13 @@ class TTDCallsWidget : public QWidget
void setParametersAndQuery(const std::string& symbols, uint64_t startAddr = 0, uint64_t endAddr = 0);
void setParametersAndQueryInNewTab(const std::string& symbols, uint64_t startAddr = 0, uint64_t endAddr = 0);

protected:
bool eventFilter(QObject* watched, QEvent* event) override;

private Q_SLOTS:
void createNewTab();
void closeTab(int index);
void renameTab(int index);
};


Expand Down
144 changes: 144 additions & 0 deletions ui/ttdmemorywidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ limitations under the License.
#include <QToolButton>
#include <QPropertyAnimation>
#include <QFrame>
#include <QTabBar>
#include <QKeyEvent>
#include <QMouseEvent>
#include <QStyleOptionTab>
#include <map>

// ColumnVisibilityDialog implementation
Expand Down Expand Up @@ -982,6 +986,8 @@ TTDMemoryWidget::TTDMemoryWidget(QWidget* parent, BinaryViewRef data)

TTDMemoryWidget::~TTDMemoryWidget()
{
// If a rename is in flight, drop the app-wide filter now rather than leave it pointing at a dead object
finishTabRename(false);
}

void TTDMemoryWidget::setupUI()
Expand All @@ -997,6 +1003,7 @@ void TTDMemoryWidget::setupUI()
m_tabWidget = new QTabWidget(this);
m_tabWidget->setTabsClosable(true);
connect(m_tabWidget, &QTabWidget::tabCloseRequested, this, &TTDMemoryWidget::closeTab);
connect(m_tabWidget, &QTabWidget::tabBarDoubleClicked, this, &TTDMemoryWidget::renameTab);

// Create "+" button as corner widget
m_newTabButton = new QToolButton(m_tabWidget);
Expand Down Expand Up @@ -1042,6 +1049,9 @@ void TTDMemoryWidget::createNewTab()

void TTDMemoryWidget::closeTab(int index)
{
// Closing any tab can shift the indices of every tab after it, so just end the rename outright
finishTabRename(false);

if (m_tabWidget->count() > 1)
{
QWidget* widget = m_tabWidget->widget(index);
Expand All @@ -1050,6 +1060,140 @@ void TTDMemoryWidget::closeTab(int index)
}
}

void TTDMemoryWidget::renameTab(int index)
{
if (index < 0)
// double-click landed on the empty strip, not a tab
return;

// Cancel any in-progress rename before starting another one
finishTabRename(false);

QTabBar* tabBar = m_tabWidget->tabBar();

m_tabRenameOriginalText = tabBar->tabText(index);
m_tabRenameWidestText = m_tabRenameOriginalText;
m_tabRenameIndex = index;

// Anchor left/top/height once - re-deriving them live let the editor drift left as the tab bar reflowed
QStyleOptionTab option;
option.initFrom(tabBar);
option.rect = tabBar->tabRect(index);
option.text = m_tabRenameOriginalText;
if (index == tabBar->currentIndex())
option.state |= QStyle::State_Selected;
if (QWidget* rightButton = tabBar->tabButton(index, QTabBar::RightSide))
option.rightButtonSize = rightButton->size();
if (QWidget* leftButton = tabBar->tabButton(index, QTabBar::LeftSide))
option.leftButtonSize = leftButton->size();

m_tabRenameBaseRect = tabBar->style()->subElementRect(QStyle::SE_TabBarTabText, &option, tabBar);

QLineEdit* editor = new QLineEdit(m_tabRenameOriginalText, tabBar);
editor->setFrame(false);
editor->setContentsMargins(0, 0, 0, 0);
editor->setTextMargins(0, 0, 0, 0);

m_tabRenameEditor = editor;
updateTabRenameGeometry();

editor->show();
editor->setFocus();
editor->selectAll();

connect(editor, &QLineEdit::textChanged, this, &TTDMemoryWidget::onTabRenameTextChanged);
connect(editor, &QLineEdit::editingFinished, this, [this]() { finishTabRename(true); });

// App-wide filter: catches Escape/focus-out on the editor and clicks anywhere else
QApplication::instance()->installEventFilter(this);
}

void TTDMemoryWidget::onTabRenameTextChanged(const QString& text)
{
if (m_tabRenameIndex < 0)
return;

QTabBar* tabBar = m_tabWidget->tabBar();

// Floor at the widest text reached; collapsing straight to empty caused a render glitch
QFontMetrics metrics(tabBar->font());
if (metrics.horizontalAdvance(text) > metrics.horizontalAdvance(m_tabRenameWidestText))
m_tabRenameWidestText = text;

bool shrinking = metrics.horizontalAdvance(text) < metrics.horizontalAdvance(m_tabRenameWidestText);
tabBar->setTabText(m_tabRenameIndex, shrinking ? m_tabRenameWidestText : text);
updateTabRenameGeometry();
}

void TTDMemoryWidget::updateTabRenameGeometry()
{
if (!m_tabRenameEditor)
return;

// Floored to match the real tab's width, or clearing the text would shrink the editor while the tab stays wide
QFontMetrics metrics(m_tabRenameEditor->font());
int textWidth = qMax(metrics.horizontalAdvance(m_tabRenameEditor->text()), metrics.horizontalAdvance(m_tabRenameWidestText));
int width = qMax(m_tabRenameBaseRect.width(), textWidth + 12);
int right = m_tabRenameBaseRect.left() + width;

// Clamp short of the close button's live position so the editor can never grow over it
if (QWidget* closeButton = m_tabWidget->tabBar()->tabButton(m_tabRenameIndex, QTabBar::RightSide))
right = qMin(right, closeButton->geometry().left() - 2);

QRect rect(m_tabRenameBaseRect.left(), m_tabRenameBaseRect.top(), qMax(0, right - m_tabRenameBaseRect.left()), m_tabRenameBaseRect.height());
m_tabRenameEditor->setGeometry(rect);
}

void TTDMemoryWidget::finishTabRename(bool commit)
{
if (!m_tabRenameEditor)
return;

QLineEdit* editor = m_tabRenameEditor;
int index = m_tabRenameIndex;
QString originalText = m_tabRenameOriginalText;
m_tabRenameEditor = nullptr;
m_tabRenameIndex = -1;

QString finalText = commit ? editor->text().trimmed() : originalText;
if (finalText.isEmpty())
finalText = originalText;

m_tabWidget->setTabText(index, finalText);
editor->deleteLater();

QApplication::instance()->removeEventFilter(this);
}

bool TTDMemoryWidget::eventFilter(QObject* watched, QEvent* event)
{
if (watched == m_tabRenameEditor)
{
if (event->type() == QEvent::KeyPress)
{
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
if (keyEvent->key() == Qt::Key_Escape)
{
finishTabRename(false);
return true;
}
}
else if (event->type() == QEvent::FocusOut)
{
finishTabRename(true);
}
}
else if (m_tabRenameEditor && event->type() == QEvent::MouseButtonPress)
{
QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
QRect editorRect(m_tabRenameEditor->mapToGlobal(QPoint(0, 0)), m_tabRenameEditor->size());
if (!editorRect.contains(mouseEvent->globalPosition().toPoint()))
finishTabRename(true);
}

return QWidget::eventFilter(watched, event);
}

TTDMemoryQueryWidget* TTDMemoryWidget::getCurrentOrNewQueryWidget()
{
// Get current tab widget
Expand Down
Loading