summaryrefslogtreecommitdiff
path: root/plugins/warp/ui/containers.cpp
diff options
context:
space:
mode:
authorMason Reed <mason@vector35.com>2025-10-07 14:32:48 -0400
committerMason Reed <mason@vector35.com>2025-10-07 14:33:34 -0400
commit4a227e0523391bc026bf96c3c31d44a39cac1701 (patch)
treecc603b899a446fb469a0be3ae4c2c77e6351c091 /plugins/warp/ui/containers.cpp
parent3e24f6ab0b373d8718e26a19ac296044cc0d19c6 (diff)
[WARP] Format UI plugin
Diffstat (limited to 'plugins/warp/ui/containers.cpp')
-rw-r--r--plugins/warp/ui/containers.cpp471
1 files changed, 240 insertions, 231 deletions
diff --git a/plugins/warp/ui/containers.cpp b/plugins/warp/ui/containers.cpp
index 689d695f..f800f977 100644
--- a/plugins/warp/ui/containers.cpp
+++ b/plugins/warp/ui/containers.cpp
@@ -1,274 +1,283 @@
#include "containers.h"
-QVariant WarpSourcesModel::data(const QModelIndex &index, int role) const
+QVariant WarpSourcesModel::data(const QModelIndex& index, int role) const
{
- if (!index.isValid())
- return {};
- if (index.row() < 0 || index.row() >= rowCount())
- return {};
+ if (!index.isValid())
+ return {};
+ if (index.row() < 0 || index.row() >= rowCount())
+ return {};
- const auto &r = m_rows[static_cast<size_t>(index.row())];
+ const auto& r = m_rows[static_cast<size_t>(index.row())];
- // Build a small two-dot status icon (left: writable, right: uncommitted)
- auto statusIcon = [](bool writable, bool uncommitted) -> QIcon {
- static QIcon cache[2][2]; // [writable][uncommitted]
- QIcon &cached = cache[writable ? 1 : 0][uncommitted ? 1 : 0];
- if (!cached.isNull())
- return cached;
+ // Build a small two-dot status icon (left: writable, right: uncommitted)
+ auto statusIcon = [](bool writable, bool uncommitted) -> QIcon {
+ static QIcon cache[2][2]; // [writable][uncommitted]
+ QIcon& cached = cache[writable ? 1 : 0][uncommitted ? 1 : 0];
+ if (!cached.isNull())
+ return cached;
- const int w = 16, h = 12, radius = 4;
- QPixmap pm(w, h);
- pm.fill(Qt::transparent);
- QPainter p(&pm);
- p.setRenderHint(QPainter::Antialiasing, true);
+ const int w = 16, h = 12, radius = 4;
+ QPixmap pm(w, h);
+ pm.fill(Qt::transparent);
+ QPainter p(&pm);
+ p.setRenderHint(QPainter::Antialiasing, true);
- // Colors
- QColor writableOn(76, 175, 80); // green
- QColor writableOff(158, 158, 158); // grey
- QColor uncommittedOn(255, 193, 7); // amber
- QColor uncommittedOff(158, 158, 158); // grey
+ // Colors
+ QColor writableOn(76, 175, 80); // green
+ QColor writableOff(158, 158, 158); // grey
+ QColor uncommittedOn(255, 193, 7); // amber
+ QColor uncommittedOff(158, 158, 158); // grey
- // Left dot: writable
- p.setBrush(writable ? writableOn : writableOff);
- p.setPen(Qt::NoPen);
- p.drawEllipse(QPoint(4, h / 2), radius, radius);
+ // Left dot: writable
+ p.setBrush(writable ? writableOn : writableOff);
+ p.setPen(Qt::NoPen);
+ p.drawEllipse(QPoint(4, h / 2), radius, radius);
- // Right dot: uncommitted
- p.setBrush(uncommitted ? uncommittedOn : uncommittedOff);
- p.drawEllipse(QPoint(w - 6, h / 2), radius, radius);
+ // Right dot: uncommitted
+ p.setBrush(uncommitted ? uncommittedOn : uncommittedOff);
+ p.drawEllipse(QPoint(w - 6, h / 2), radius, radius);
- p.end();
- cached = QIcon(pm);
- return cached;
- };
+ p.end();
+ cached = QIcon(pm);
+ return cached;
+ };
- if (role == Qt::DecorationRole && index.column() == PathCol)
- {
- return statusIcon(r.writable, r.uncommitted);
- }
+ if (role == Qt::DecorationRole && index.column() == PathCol)
+ {
+ return statusIcon(r.writable, r.uncommitted);
+ }
- if (role == Qt::ToolTipRole && index.column() == PathCol)
- {
- QStringList parts;
- parts << (r.writable ? "Writable" : "Read-only");
- parts << (r.uncommitted ? "Uncommitted changes" : "No uncommitted changes");
- return parts.join(" • ");
- }
+ if (role == Qt::ToolTipRole && index.column() == PathCol)
+ {
+ QStringList parts;
+ parts << (r.writable ? "Writable" : "Read-only");
+ parts << (r.uncommitted ? "Uncommitted changes" : "No uncommitted changes");
+ return parts.join(" • ");
+ }
- if (role == Qt::DisplayRole)
- {
- switch (index.column())
- {
- case GuidCol: return r.guid;
- case PathCol: return r.path;
- case WritableCol: return r.writable ? "Yes" : "No";
- case UncommittedCol: return r.uncommitted ? "Yes" : "No";
- default: return {};
- }
- }
+ if (role == Qt::DisplayRole)
+ {
+ switch (index.column())
+ {
+ case GuidCol:
+ return r.guid;
+ case PathCol:
+ return r.path;
+ case WritableCol:
+ return r.writable ? "Yes" : "No";
+ case UncommittedCol:
+ return r.uncommitted ? "Yes" : "No";
+ default:
+ return {};
+ }
+ }
- if (role == Qt::CheckStateRole)
- {
- // Optional: expose as checkboxes if someone ever shows these columns
- switch (index.column())
- {
- case WritableCol: return r.writable ? Qt::Checked : Qt::Unchecked;
- case UncommittedCol: return r.uncommitted ? Qt::Checked : Qt::Unchecked;
- default: break;
- }
- }
+ if (role == Qt::CheckStateRole)
+ {
+ // Optional: expose as checkboxes if someone ever shows these columns
+ switch (index.column())
+ {
+ case WritableCol:
+ return r.writable ? Qt::Checked : Qt::Unchecked;
+ case UncommittedCol:
+ return r.uncommitted ? Qt::Checked : Qt::Unchecked;
+ default:
+ break;
+ }
+ }
- return {};
+ return {};
}
-WarpContainerWidget::WarpContainerWidget(Warp::Ref<Warp::Container> container, QWidget *parent) : QWidget(parent)
+WarpContainerWidget::WarpContainerWidget(Warp::Ref<Warp::Container> container, QWidget* parent) : QWidget(parent)
{
- m_container = std::move(container);
- auto *layout = new QVBoxLayout(this);
- layout->setContentsMargins(0, 0, 0, 0);
- m_tabs = new QTabWidget(this);
- layout->addWidget(m_tabs);
+ m_container = std::move(container);
+ auto* layout = new QVBoxLayout(this);
+ layout->setContentsMargins(0, 0, 0, 0);
+ m_tabs = new QTabWidget(this);
+ layout->addWidget(m_tabs);
- // Sources tab
- m_sourcesPage = new QWidget(this);
- auto *sourcesLayout = new QVBoxLayout(m_sourcesPage);
- m_sourcesView = new QTableView(m_sourcesPage);
- m_sourcesModel = new WarpSourcesModel(m_sourcesPage);
- m_sourcesModel->setContainer(m_container);
- m_sourcesView->setModel(m_sourcesModel);
- m_sourcesView->horizontalHeader()->setStretchLastSection(true);
- m_sourcesView->setSelectionBehavior(QAbstractItemView::SelectRows);
- m_sourcesView->setSelectionMode(QAbstractItemView::SingleSelection);
+ // Sources tab
+ m_sourcesPage = new QWidget(this);
+ auto* sourcesLayout = new QVBoxLayout(m_sourcesPage);
+ m_sourcesView = new QTableView(m_sourcesPage);
+ m_sourcesModel = new WarpSourcesModel(m_sourcesPage);
+ m_sourcesModel->setContainer(m_container);
+ m_sourcesView->setModel(m_sourcesModel);
+ m_sourcesView->horizontalHeader()->setStretchLastSection(true);
+ m_sourcesView->setSelectionBehavior(QAbstractItemView::SelectRows);
+ m_sourcesView->setSelectionMode(QAbstractItemView::SingleSelection);
- // Make the table look like a simple list that shows only the source path
- m_sourcesView->setShowGrid(false);
- m_sourcesView->verticalHeader()->setVisible(false);
- m_sourcesView->horizontalHeader()->setVisible(false);
- m_sourcesView->setAlternatingRowColors(false);
- m_sourcesView->setEditTriggers(QAbstractItemView::NoEditTriggers);
- m_sourcesView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
- m_sourcesView->setWordWrap(false);
- m_sourcesView->setIconSize(QSize(16, 12));
- // Ensure long paths truncate from the left: "...tail/of/the/path"
- m_sourcesView->setTextElideMode(Qt::ElideLeft);
- // Hide GUID column, keep only the Path column visible
- m_sourcesView->setColumnHidden(WarpSourcesModel::GuidCol, true);
- // Also hide boolean columns; their state is shown as an icon next to the path
- m_sourcesView->setColumnHidden(WarpSourcesModel::WritableCol, true);
- m_sourcesView->setColumnHidden(WarpSourcesModel::UncommittedCol, true);
- // Ensure the remaining (Path) column fills the width
- m_sourcesView->horizontalHeader()->setSectionResizeMode(WarpSourcesModel::PathCol, QHeaderView::Stretch);
+ // Make the table look like a simple list that shows only the source path
+ m_sourcesView->setShowGrid(false);
+ m_sourcesView->verticalHeader()->setVisible(false);
+ m_sourcesView->horizontalHeader()->setVisible(false);
+ m_sourcesView->setAlternatingRowColors(false);
+ m_sourcesView->setEditTriggers(QAbstractItemView::NoEditTriggers);
+ m_sourcesView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+ m_sourcesView->setWordWrap(false);
+ m_sourcesView->setIconSize(QSize(16, 12));
+ // Ensure long paths truncate from the left: "...tail/of/the/path"
+ m_sourcesView->setTextElideMode(Qt::ElideLeft);
+ // Hide GUID column, keep only the Path column visible
+ m_sourcesView->setColumnHidden(WarpSourcesModel::GuidCol, true);
+ // Also hide boolean columns; their state is shown as an icon next to the path
+ m_sourcesView->setColumnHidden(WarpSourcesModel::WritableCol, true);
+ m_sourcesView->setColumnHidden(WarpSourcesModel::UncommittedCol, true);
+ // Ensure the remaining (Path) column fills the width
+ m_sourcesView->horizontalHeader()->setSectionResizeMode(WarpSourcesModel::PathCol, QHeaderView::Stretch);
- // Per-item context menu
- m_sourcesView->setContextMenuPolicy(Qt::CustomContextMenu);
- connect(m_sourcesView, &QWidget::customContextMenuRequested, this, [this](const QPoint &pos) {
- QMenu menu(m_sourcesView);
- const QModelIndex index = m_sourcesView->indexAt(pos);
+ // Per-item context menu
+ m_sourcesView->setContextMenuPolicy(Qt::CustomContextMenu);
+ connect(m_sourcesView, &QWidget::customContextMenuRequested, this, [this](const QPoint& pos) {
+ QMenu menu(m_sourcesView);
+ const QModelIndex index = m_sourcesView->indexAt(pos);
- if (!index.isValid())
- {
- QAction *actAdd = menu.addAction(tr("Add Source"));
- QAction *chosen = menu.exec(m_sourcesView->viewport()->mapToGlobal(pos));
- if (!chosen)
- return;
- if (chosen == actAdd)
- {
- std::string sourceName;
- if (!BinaryNinja::GetTextLineInput(sourceName, "Source name:", "Add Source"))
- return;
- if (const auto sourceId = m_container->AddSource(sourceName); !sourceId.has_value())
- {
- BinaryNinja::LogAlertF("Failed to add source: {}", sourceName);
- return;
- }
- m_sourcesModel->reload();
- }
- } else
- {
- m_sourcesView->setCurrentIndex(index.sibling(index.row(), WarpSourcesModel::PathCol));
+ if (!index.isValid())
+ {
+ QAction* actAdd = menu.addAction(tr("Add Source"));
+ QAction* chosen = menu.exec(m_sourcesView->viewport()->mapToGlobal(pos));
+ if (!chosen)
+ return;
+ if (chosen == actAdd)
+ {
+ std::string sourceName;
+ if (!BinaryNinja::GetTextLineInput(sourceName, "Source name:", "Add Source"))
+ return;
+ if (const auto sourceId = m_container->AddSource(sourceName); !sourceId.has_value())
+ {
+ BinaryNinja::LogAlertF("Failed to add source: {}", sourceName);
+ return;
+ }
+ m_sourcesModel->reload();
+ }
+ }
+ else
+ {
+ m_sourcesView->setCurrentIndex(index.sibling(index.row(), WarpSourcesModel::PathCol));
- const int row = index.row();
- const QModelIndex pathIdx = m_sourcesModel->index(row, WarpSourcesModel::PathCol);
- const QModelIndex guidIdx = m_sourcesModel->index(row, WarpSourcesModel::GuidCol);
- const QString path = m_sourcesModel->data(pathIdx, Qt::DisplayRole).toString();
- const QFileInfo fi(path);
-
- const QString guid = m_sourcesModel->data(guidIdx, Qt::DisplayRole).toString();
+ const int row = index.row();
+ const QModelIndex pathIdx = m_sourcesModel->index(row, WarpSourcesModel::PathCol);
+ const QModelIndex guidIdx = m_sourcesModel->index(row, WarpSourcesModel::GuidCol);
+ const QString path = m_sourcesModel->data(pathIdx, Qt::DisplayRole).toString();
+ const QFileInfo fi(path);
- QAction *actReveal = menu.addAction(tr("Reveal in File Browser"));
- actReveal->setEnabled(fi.exists());
- QAction *actCopyPath = menu.addAction(tr("Copy Path"));
- QAction *actCopyGuid = menu.addAction(tr("Copy GUID"));
-
- QAction *chosen = menu.exec(m_sourcesView->viewport()->mapToGlobal(pos));
- if (!chosen)
- return;
- if (chosen == actCopyPath)
- QGuiApplication::clipboard()->setText(path);
- else if (chosen == actCopyGuid)
- QGuiApplication::clipboard()->setText(guid);
- else if (chosen == actReveal)
- QDesktopServices::openUrl(QUrl::fromLocalFile(fi.absoluteFilePath()));
- }
- });
+ const QString guid = m_sourcesModel->data(guidIdx, Qt::DisplayRole).toString();
+ QAction* actReveal = menu.addAction(tr("Reveal in File Browser"));
+ actReveal->setEnabled(fi.exists());
+ QAction* actCopyPath = menu.addAction(tr("Copy Path"));
+ QAction* actCopyGuid = menu.addAction(tr("Copy GUID"));
- sourcesLayout->addWidget(m_sourcesView);
- m_tabs->addTab(m_sourcesPage, tr("Sources"));
+ QAction* chosen = menu.exec(m_sourcesView->viewport()->mapToGlobal(pos));
+ if (!chosen)
+ return;
+ if (chosen == actCopyPath)
+ QGuiApplication::clipboard()->setText(path);
+ else if (chosen == actCopyGuid)
+ QGuiApplication::clipboard()->setText(guid);
+ else if (chosen == actReveal)
+ QDesktopServices::openUrl(QUrl::fromLocalFile(fi.absoluteFilePath()));
+ }
+ });
- // Search tab
- m_searchTab = new WarpSearchWidget(m_container, this);
- m_tabs->addTab(m_searchTab, tr("Search"));
- // Periodic refresh timer for the Sources view
- m_refreshTimer = new QTimer(this);
- m_refreshTimer->setInterval(5000);
- connect(m_refreshTimer, &QTimer::timeout, this, [this]() {
- // Only refresh if the widget and the Sources page are actually visible
- if (!this->isVisible() || !m_sourcesPage || !m_sourcesPage->isVisible())
- return;
+ sourcesLayout->addWidget(m_sourcesView);
+ m_tabs->addTab(m_sourcesPage, tr("Sources"));
- // Preserve selection by GUID across reloads
- QString currentGuid;
- if (const QModelIndex currentIdx = m_sourcesView->currentIndex(); currentIdx.isValid())
- {
- const int row = currentIdx.row();
- const QModelIndex guidIdx = m_sourcesModel->index(row, WarpSourcesModel::GuidCol);
- currentGuid = m_sourcesModel->data(guidIdx, Qt::DisplayRole).toString();
- }
+ // Search tab
+ m_searchTab = new WarpSearchWidget(m_container, this);
+ m_tabs->addTab(m_searchTab, tr("Search"));
- m_sourcesModel->reload();
+ // Periodic refresh timer for the Sources view
+ m_refreshTimer = new QTimer(this);
+ m_refreshTimer->setInterval(5000);
+ connect(m_refreshTimer, &QTimer::timeout, this, [this]() {
+ // Only refresh if the widget and the Sources page are actually visible
+ if (!this->isVisible() || !m_sourcesPage || !m_sourcesPage->isVisible())
+ return;
- if (!currentGuid.isEmpty())
- {
- for (int r = 0; r < m_sourcesModel->rowCount(); ++r)
- {
- const QModelIndex gIdx = m_sourcesModel->index(r, WarpSourcesModel::GuidCol);
- if (m_sourcesModel->data(gIdx, Qt::DisplayRole).toString() == currentGuid)
- {
- m_sourcesView->setCurrentIndex(m_sourcesModel->index(r, WarpSourcesModel::PathCol));
- break;
- }
- }
- }
- });
- m_refreshTimer->start();
+ // Preserve selection by GUID across reloads
+ QString currentGuid;
+ if (const QModelIndex currentIdx = m_sourcesView->currentIndex(); currentIdx.isValid())
+ {
+ const int row = currentIdx.row();
+ const QModelIndex guidIdx = m_sourcesModel->index(row, WarpSourcesModel::GuidCol);
+ currentGuid = m_sourcesModel->data(guidIdx, Qt::DisplayRole).toString();
+ }
- // Optional: force a refresh when switching back to the Sources tab
- connect(m_tabs, &QTabWidget::currentChanged, this, [this](const int idx) {
- QWidget *w = m_tabs->widget(idx);
- if (w == m_sourcesPage)
- m_sourcesModel->reload();
- });
+ m_sourcesModel->reload();
+
+ if (!currentGuid.isEmpty())
+ {
+ for (int r = 0; r < m_sourcesModel->rowCount(); ++r)
+ {
+ const QModelIndex gIdx = m_sourcesModel->index(r, WarpSourcesModel::GuidCol);
+ if (m_sourcesModel->data(gIdx, Qt::DisplayRole).toString() == currentGuid)
+ {
+ m_sourcesView->setCurrentIndex(m_sourcesModel->index(r, WarpSourcesModel::PathCol));
+ break;
+ }
+ }
+ }
+ });
+ m_refreshTimer->start();
+
+ // Optional: force a refresh when switching back to the Sources tab
+ connect(m_tabs, &QTabWidget::currentChanged, this, [this](const int idx) {
+ QWidget* w = m_tabs->widget(idx);
+ if (w == m_sourcesPage)
+ m_sourcesModel->reload();
+ });
}
-WarpContainersPane::WarpContainersPane(QWidget *parent) : QWidget(parent)
+WarpContainersPane::WarpContainersPane(QWidget* parent) : QWidget(parent)
{
- auto *splitter = new QSplitter(Qt::Vertical, this);
- splitter->setContentsMargins(0, 0, 0, 0);
- auto *mainLayout = new QVBoxLayout(this);
- mainLayout->setContentsMargins(0, 0, 0, 0);
- mainLayout->setSpacing(0);
- mainLayout->addWidget(splitter);
- auto newPalette = palette();
- newPalette.setColor(QPalette::Window, getThemeColor(SidebarWidgetBackgroundColor));
- setAutoFillBackground(true);
- setPalette(newPalette);
+ auto* splitter = new QSplitter(Qt::Vertical, this);
+ splitter->setContentsMargins(0, 0, 0, 0);
+ auto* mainLayout = new QVBoxLayout(this);
+ mainLayout->setContentsMargins(0, 0, 0, 0);
+ mainLayout->setSpacing(0);
+ mainLayout->addWidget(splitter);
+ auto newPalette = palette();
+ newPalette.setColor(QPalette::Window, getThemeColor(SidebarWidgetBackgroundColor));
+ setAutoFillBackground(true);
+ setPalette(newPalette);
- // List on top
- m_list = new QListWidget(splitter);
- m_list->setSelectionMode(QAbstractItemView::SingleSelection);
- m_list->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
- m_list->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents);
- m_list->setUniformItemSizes(true);
+ // List on top
+ m_list = new QListWidget(splitter);
+ m_list->setSelectionMode(QAbstractItemView::SingleSelection);
+ m_list->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+ m_list->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents);
+ m_list->setUniformItemSizes(true);
- // Make names larger and show end of long strings (elide at the start)
- {
- QFont f = m_list->font();
- f.setPointSizeF(f.pointSizeF() + 2.0); // bump size
- m_list->setFont(f);
- m_list->setTextElideMode(Qt::ElideLeft);
- }
+ // Make names larger and show end of long strings (elide at the start)
+ {
+ QFont f = m_list->font();
+ f.setPointSizeF(f.pointSizeF() + 2.0); // bump size
+ m_list->setFont(f);
+ m_list->setTextElideMode(Qt::ElideLeft);
+ }
- // Container view (tabs) below
- m_stack = new QStackedWidget(splitter);
- m_stack->setContentsMargins(0, 0, 0, 0);
+ // Container view (tabs) below
+ m_stack = new QStackedWidget(splitter);
+ m_stack->setContentsMargins(0, 0, 0, 0);
- splitter->setStretchFactor(0, 0); // list: minimal growth
- splitter->setStretchFactor(1, 1); // stack: takes remaining space
- splitter->setCollapsible(0, false);
- splitter->setCollapsible(1, false);
+ splitter->setStretchFactor(0, 0); // list: minimal growth
+ splitter->setStretchFactor(1, 1); // stack: takes remaining space
+ splitter->setCollapsible(0, false);
+ splitter->setCollapsible(1, false);
- populate();
+ populate();
- connect(m_list, &QListWidget::currentRowChanged, this, [this](int row) {
- if (row >= 0 && row < m_stack->count())
- m_stack->setCurrentIndex(row);
- });
+ connect(m_list, &QListWidget::currentRowChanged, this, [this](int row) {
+ if (row >= 0 && row < m_stack->count())
+ m_stack->setCurrentIndex(row);
+ });
- // Select the first container if available
- if (m_list->count() > 0)
- {
- m_list->setCurrentRow(0);
- }
+ // Select the first container if available
+ if (m_list->count() > 0)
+ {
+ m_list->setCurrentRow(0);
+ }
}