Skip to content

Commit 5516cd3

Browse files
committed
blh
1 parent bebe7de commit 5516cd3

File tree

2 files changed

+288
-166
lines changed

2 files changed

+288
-166
lines changed

plugins/warp/ui/containers.cpp

Lines changed: 274 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,274 @@
1-
#include "containers.h"
1+
#include "containers.h"
2+
3+
QVariant WarpSourcesModel::data(const QModelIndex &index, int role) const
4+
{
5+
if (!index.isValid())
6+
return {};
7+
if (index.row() < 0 || index.row() >= rowCount())
8+
return {};
9+
10+
const auto &r = m_rows[static_cast<size_t>(index.row())];
11+
12+
// Build a small two-dot status icon (left: writable, right: uncommitted)
13+
auto statusIcon = [](bool writable, bool uncommitted) -> QIcon {
14+
static QIcon cache[2][2]; // [writable][uncommitted]
15+
QIcon &cached = cache[writable ? 1 : 0][uncommitted ? 1 : 0];
16+
if (!cached.isNull())
17+
return cached;
18+
19+
const int w = 16, h = 12, radius = 4;
20+
QPixmap pm(w, h);
21+
pm.fill(Qt::transparent);
22+
QPainter p(&pm);
23+
p.setRenderHint(QPainter::Antialiasing, true);
24+
25+
// Colors
26+
QColor writableOn(76, 175, 80); // green
27+
QColor writableOff(158, 158, 158); // grey
28+
QColor uncommittedOn(255, 193, 7); // amber
29+
QColor uncommittedOff(158, 158, 158); // grey
30+
31+
// Left dot: writable
32+
p.setBrush(writable ? writableOn : writableOff);
33+
p.setPen(Qt::NoPen);
34+
p.drawEllipse(QPoint(4, h / 2), radius, radius);
35+
36+
// Right dot: uncommitted
37+
p.setBrush(uncommitted ? uncommittedOn : uncommittedOff);
38+
p.drawEllipse(QPoint(w - 6, h / 2), radius, radius);
39+
40+
p.end();
41+
cached = QIcon(pm);
42+
return cached;
43+
};
44+
45+
if (role == Qt::DecorationRole && index.column() == PathCol)
46+
{
47+
return statusIcon(r.writable, r.uncommitted);
48+
}
49+
50+
if (role == Qt::ToolTipRole && index.column() == PathCol)
51+
{
52+
QStringList parts;
53+
parts << (r.writable ? "Writable" : "Read-only");
54+
parts << (r.uncommitted ? "Uncommitted changes" : "No uncommitted changes");
55+
return parts.join("");
56+
}
57+
58+
if (role == Qt::DisplayRole)
59+
{
60+
switch (index.column())
61+
{
62+
case GuidCol: return r.guid;
63+
case PathCol: return r.path;
64+
case WritableCol: return r.writable ? "Yes" : "No";
65+
case UncommittedCol: return r.uncommitted ? "Yes" : "No";
66+
default: return {};
67+
}
68+
}
69+
70+
if (role == Qt::CheckStateRole)
71+
{
72+
// Optional: expose as checkboxes if someone ever shows these columns
73+
switch (index.column())
74+
{
75+
case WritableCol: return r.writable ? Qt::Checked : Qt::Unchecked;
76+
case UncommittedCol: return r.uncommitted ? Qt::Checked : Qt::Unchecked;
77+
default: break;
78+
}
79+
}
80+
81+
return {};
82+
}
83+
84+
WarpContainerWidget::WarpContainerWidget(Warp::Ref<Warp::Container> container, QWidget *parent) : QWidget(parent)
85+
{
86+
m_container = std::move(container);
87+
auto *layout = new QVBoxLayout(this);
88+
layout->setContentsMargins(0, 0, 0, 0);
89+
m_tabs = new QTabWidget(this);
90+
layout->addWidget(m_tabs);
91+
92+
// Sources tab
93+
m_sourcesPage = new QWidget(this);
94+
auto *sourcesLayout = new QVBoxLayout(m_sourcesPage);
95+
m_sourcesView = new QTableView(m_sourcesPage);
96+
m_sourcesModel = new WarpSourcesModel(m_sourcesPage);
97+
m_sourcesModel->setContainer(m_container);
98+
m_sourcesView->setModel(m_sourcesModel);
99+
m_sourcesView->horizontalHeader()->setStretchLastSection(true);
100+
m_sourcesView->setSelectionBehavior(QAbstractItemView::SelectRows);
101+
m_sourcesView->setSelectionMode(QAbstractItemView::SingleSelection);
102+
103+
// Make the table look like a simple list that shows only the source path
104+
m_sourcesView->setShowGrid(false);
105+
m_sourcesView->verticalHeader()->setVisible(false);
106+
m_sourcesView->horizontalHeader()->setVisible(false);
107+
m_sourcesView->setAlternatingRowColors(false);
108+
m_sourcesView->setEditTriggers(QAbstractItemView::NoEditTriggers);
109+
m_sourcesView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
110+
m_sourcesView->setWordWrap(false);
111+
m_sourcesView->setIconSize(QSize(16, 12));
112+
// Ensure long paths truncate from the left: "...tail/of/the/path"
113+
m_sourcesView->setTextElideMode(Qt::ElideLeft);
114+
// Hide GUID column, keep only the Path column visible
115+
m_sourcesView->setColumnHidden(WarpSourcesModel::GuidCol, true);
116+
// Also hide boolean columns; their state is shown as an icon next to the path
117+
m_sourcesView->setColumnHidden(WarpSourcesModel::WritableCol, true);
118+
m_sourcesView->setColumnHidden(WarpSourcesModel::UncommittedCol, true);
119+
// Ensure the remaining (Path) column fills the width
120+
m_sourcesView->horizontalHeader()->setSectionResizeMode(WarpSourcesModel::PathCol, QHeaderView::Stretch);
121+
122+
// Per-item context menu
123+
m_sourcesView->setContextMenuPolicy(Qt::CustomContextMenu);
124+
connect(m_sourcesView, &QWidget::customContextMenuRequested, this, [this](const QPoint &pos) {
125+
QMenu menu(m_sourcesView);
126+
const QModelIndex index = m_sourcesView->indexAt(pos);
127+
128+
if (!index.isValid())
129+
{
130+
QAction *actAdd = menu.addAction(tr("Add Source"));
131+
QAction *chosen = menu.exec(m_sourcesView->viewport()->mapToGlobal(pos));
132+
if (!chosen)
133+
return;
134+
if (chosen == actAdd)
135+
{
136+
std::string sourceName;
137+
if (!BinaryNinja::GetTextLineInput(sourceName, "Source name:", "Add Source"))
138+
return;
139+
if (const auto sourceId = m_container->AddSource(sourceName); !sourceId.has_value())
140+
{
141+
BinaryNinja::LogAlertF("Failed to add source: {}", sourceName);
142+
return;
143+
}
144+
m_sourcesModel->reload();
145+
}
146+
} else
147+
{
148+
m_sourcesView->setCurrentIndex(index.sibling(index.row(), WarpSourcesModel::PathCol));
149+
150+
const int row = index.row();
151+
const QModelIndex pathIdx = m_sourcesModel->index(row, WarpSourcesModel::PathCol);
152+
const QModelIndex guidIdx = m_sourcesModel->index(row, WarpSourcesModel::GuidCol);
153+
const QString path = m_sourcesModel->data(pathIdx, Qt::DisplayRole).toString();
154+
const QFileInfo fi(path);
155+
156+
const QString guid = m_sourcesModel->data(guidIdx, Qt::DisplayRole).toString();
157+
158+
QAction *actReveal = menu.addAction(tr("Reveal in File Browser"));
159+
actReveal->setEnabled(fi.exists());
160+
QAction *actCopyPath = menu.addAction(tr("Copy Path"));
161+
QAction *actCopyGuid = menu.addAction(tr("Copy GUID"));
162+
163+
QAction *chosen = menu.exec(m_sourcesView->viewport()->mapToGlobal(pos));
164+
if (!chosen)
165+
return;
166+
if (chosen == actCopyPath)
167+
QGuiApplication::clipboard()->setText(path);
168+
else if (chosen == actCopyGuid)
169+
QGuiApplication::clipboard()->setText(guid);
170+
else if (chosen == actReveal)
171+
QDesktopServices::openUrl(QUrl::fromLocalFile(fi.absoluteFilePath()));
172+
}
173+
});
174+
175+
176+
sourcesLayout->addWidget(m_sourcesView);
177+
m_tabs->addTab(m_sourcesPage, tr("Sources"));
178+
179+
// Search tab
180+
m_searchTab = new WarpSearchWidget(m_container, this);
181+
m_tabs->addTab(m_searchTab, tr("Search"));
182+
183+
// Periodic refresh timer for the Sources view
184+
m_refreshTimer = new QTimer(this);
185+
m_refreshTimer->setInterval(5000);
186+
connect(m_refreshTimer, &QTimer::timeout, this, [this]() {
187+
// Only refresh if the widget and the Sources page are actually visible
188+
if (!this->isVisible() || !m_sourcesPage || !m_sourcesPage->isVisible())
189+
return;
190+
191+
// Preserve selection by GUID across reloads
192+
QString currentGuid;
193+
if (const QModelIndex currentIdx = m_sourcesView->currentIndex(); currentIdx.isValid())
194+
{
195+
const int row = currentIdx.row();
196+
const QModelIndex guidIdx = m_sourcesModel->index(row, WarpSourcesModel::GuidCol);
197+
currentGuid = m_sourcesModel->data(guidIdx, Qt::DisplayRole).toString();
198+
}
199+
200+
m_sourcesModel->reload();
201+
202+
if (!currentGuid.isEmpty())
203+
{
204+
for (int r = 0; r < m_sourcesModel->rowCount(); ++r)
205+
{
206+
const QModelIndex gIdx = m_sourcesModel->index(r, WarpSourcesModel::GuidCol);
207+
if (m_sourcesModel->data(gIdx, Qt::DisplayRole).toString() == currentGuid)
208+
{
209+
m_sourcesView->setCurrentIndex(m_sourcesModel->index(r, WarpSourcesModel::PathCol));
210+
break;
211+
}
212+
}
213+
}
214+
});
215+
m_refreshTimer->start();
216+
217+
// Optional: force a refresh when switching back to the Sources tab
218+
connect(m_tabs, &QTabWidget::currentChanged, this, [this](const int idx) {
219+
QWidget *w = m_tabs->widget(idx);
220+
if (w == m_sourcesPage)
221+
m_sourcesModel->reload();
222+
});
223+
}
224+
225+
WarpContainersPane::WarpContainersPane(QWidget *parent) : QWidget(parent)
226+
{
227+
auto *splitter = new QSplitter(Qt::Vertical, this);
228+
splitter->setContentsMargins(0, 0, 0, 0);
229+
auto *mainLayout = new QVBoxLayout(this);
230+
mainLayout->setContentsMargins(0, 0, 0, 0);
231+
mainLayout->setSpacing(0);
232+
mainLayout->addWidget(splitter);
233+
auto newPalette = palette();
234+
newPalette.setColor(QPalette::Window, getThemeColor(SidebarWidgetBackgroundColor));
235+
setAutoFillBackground(true);
236+
setPalette(newPalette);
237+
238+
// List on top
239+
m_list = new QListWidget(splitter);
240+
m_list->setSelectionMode(QAbstractItemView::SingleSelection);
241+
m_list->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
242+
m_list->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents);
243+
m_list->setUniformItemSizes(true);
244+
245+
// Make names larger and show end of long strings (elide at the start)
246+
{
247+
QFont f = m_list->font();
248+
f.setPointSizeF(f.pointSizeF() + 2.0); // bump size
249+
m_list->setFont(f);
250+
m_list->setTextElideMode(Qt::ElideLeft);
251+
}
252+
253+
// Container view (tabs) below
254+
m_stack = new QStackedWidget(splitter);
255+
m_stack->setContentsMargins(0, 0, 0, 0);
256+
257+
splitter->setStretchFactor(0, 0); // list: minimal growth
258+
splitter->setStretchFactor(1, 1); // stack: takes remaining space
259+
splitter->setCollapsible(0, false);
260+
splitter->setCollapsible(1, false);
261+
262+
populate();
263+
264+
connect(m_list, &QListWidget::currentRowChanged, this, [this](int row) {
265+
if (row >= 0 && row < m_stack->count())
266+
m_stack->setCurrentIndex(row);
267+
});
268+
269+
// Select the first container if available
270+
if (m_list->count() > 0)
271+
{
272+
m_list->setCurrentRow(0);
273+
}
274+
}

0 commit comments

Comments
 (0)