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
146 changes: 145 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,138 @@ 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;

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();

// Never shrink past the widest point reached this session; a big single-step collapse
// (e.g. selecting a long typed name and deleting it) is what 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;

QTabBar* tabBar = m_tabWidget->tabBar();
int index = m_tabRenameIndex;

// Text-only sub-rect; passing the close button's size keeps it from overlapping the button
QStyleOptionTab option;
option.initFrom(tabBar);
option.rect = tabBar->tabRect(index);
option.text = tabBar->tabText(index);
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();

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

const int padding = 6;
textRect.adjust(-padding / 2, 0, padding / 2, 0);
textRect = textRect.intersected(option.rect.adjusted(1, 1, -1, -1));

m_tabRenameEditor->setGeometry(textRect);
}

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
11 changes: 11 additions & 0 deletions ui/ttdcallswidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,15 @@ 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;

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

public:
TTDCallsWidget(QWidget* parent, BinaryViewRef data);
Expand All @@ -163,9 +170,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
142 changes: 142 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,138 @@ 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;

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();

// Never shrink past the widest point reached this session; a big single-step collapse
// (e.g. selecting a long typed name and deleting it) is what 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;

QTabBar* tabBar = m_tabWidget->tabBar();
int index = m_tabRenameIndex;

// Text-only sub-rect; passing the close button's size keeps it from overlapping the button
QStyleOptionTab option;
option.initFrom(tabBar);
option.rect = tabBar->tabRect(index);
option.text = tabBar->tabText(index);
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();

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

const int padding = 6;
textRect.adjust(-padding / 2, 0, padding / 2, 0);
textRect = textRect.intersected(option.rect.adjusted(1, 1, -1, -1));

m_tabRenameEditor->setGeometry(textRect);
}

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