diff options
| author | Mason Reed <mason@vector35.com> | 2025-10-07 14:32:48 -0400 |
|---|---|---|
| committer | Mason Reed <mason@vector35.com> | 2025-10-07 14:33:34 -0400 |
| commit | 4a227e0523391bc026bf96c3c31d44a39cac1701 (patch) | |
| tree | cc603b899a446fb469a0be3ae4c2c77e6351c091 /plugins/warp/ui/containers.cpp | |
| parent | 3e24f6ab0b373d8718e26a19ac296044cc0d19c6 (diff) | |
[WARP] Format UI plugin
Diffstat (limited to 'plugins/warp/ui/containers.cpp')
| -rw-r--r-- | plugins/warp/ui/containers.cpp | 471 |
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); + } } |
