mirror of
https://github.com/NohamR/Reclass.git
synced 2026-05-10 19:59:21 +00:00
refactor: process picker themed styling, context menu, auto-select
Extract shared init into initUi(). Apply dark theme styling from global palette to table, header, filter, and buttons. Add right-click context menu with Copy PID/Name/Path. Auto-select last attached process on open. Remove duplicate attach->accept() connection from .ui (handled in code).
This commit is contained in:
@@ -5,6 +5,10 @@
|
|||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QPixmap>
|
#include <QPixmap>
|
||||||
|
#include <QSettings>
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QClipboard>
|
||||||
|
#include <QMenu>
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
@@ -27,22 +31,9 @@ ProcessPicker::ProcessPicker(QWidget *parent)
|
|||||||
, m_useCustomList(false)
|
, m_useCustomList(false)
|
||||||
{
|
{
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
|
initUi();
|
||||||
// Configure table
|
|
||||||
ui->processTable->setColumnWidth(0, 80); // PID column - fixed width
|
|
||||||
ui->processTable->setColumnWidth(1, 200); // Name column - fixed width
|
|
||||||
ui->processTable->horizontalHeader()->setStretchLastSection(true); // Path column - fills remaining space
|
|
||||||
ui->processTable->setWordWrap(false); // Disable word wrap for single-line display
|
|
||||||
ui->processTable->setTextElideMode(Qt::ElideLeft); // Elide from left (show end of path)
|
|
||||||
|
|
||||||
// Connect signals
|
|
||||||
connect(ui->refreshButton, &QPushButton::clicked, this, &ProcessPicker::refreshProcessList);
|
|
||||||
connect(ui->processTable, &QTableWidget::itemDoubleClicked, this, &ProcessPicker::onProcessSelected);
|
|
||||||
connect(ui->filterEdit, &QLineEdit::textChanged, this, &ProcessPicker::filterProcesses);
|
|
||||||
connect(ui->attachButton, &QPushButton::clicked, this, &ProcessPicker::onProcessSelected);
|
|
||||||
|
|
||||||
// Initial process enumeration
|
|
||||||
refreshProcessList();
|
refreshProcessList();
|
||||||
|
selectPreferredProcess();
|
||||||
}
|
}
|
||||||
|
|
||||||
ProcessPicker::ProcessPicker(const QList<ProcessInfo>& customProcesses, QWidget *parent)
|
ProcessPicker::ProcessPicker(const QList<ProcessInfo>& customProcesses, QWidget *parent)
|
||||||
@@ -51,23 +42,102 @@ ProcessPicker::ProcessPicker(const QList<ProcessInfo>& customProcesses, QWidget
|
|||||||
, m_useCustomList(true)
|
, m_useCustomList(true)
|
||||||
{
|
{
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
|
initUi();
|
||||||
|
ui->refreshButton->setVisible(false);
|
||||||
|
m_allProcesses = customProcesses;
|
||||||
|
applyFilter();
|
||||||
|
selectPreferredProcess();
|
||||||
|
}
|
||||||
|
|
||||||
// Configure table
|
void ProcessPicker::initUi()
|
||||||
ui->processTable->setColumnWidth(0, 80);
|
{
|
||||||
ui->processTable->setColumnWidth(1, 200);
|
// Table configuration
|
||||||
|
ui->processTable->setColumnWidth(0, 80); // PID column
|
||||||
|
ui->processTable->setColumnWidth(1, 200); // Name column
|
||||||
ui->processTable->horizontalHeader()->setStretchLastSection(true);
|
ui->processTable->horizontalHeader()->setStretchLastSection(true);
|
||||||
ui->processTable->setWordWrap(false);
|
ui->processTable->setWordWrap(false);
|
||||||
ui->processTable->setTextElideMode(Qt::ElideLeft);
|
ui->processTable->setTextElideMode(Qt::ElideLeft);
|
||||||
|
ui->processTable->setShowGrid(false);
|
||||||
|
ui->processTable->verticalHeader()->setDefaultSectionSize(fontMetrics().height() + 6);
|
||||||
|
|
||||||
// Connect signals (no refresh button for custom lists)
|
// Signal connections
|
||||||
ui->refreshButton->setVisible(false);
|
connect(ui->refreshButton, &QPushButton::clicked, this, &ProcessPicker::refreshProcessList);
|
||||||
connect(ui->processTable, &QTableWidget::itemDoubleClicked, this, &ProcessPicker::onProcessSelected);
|
connect(ui->processTable, &QTableWidget::itemDoubleClicked, this, &ProcessPicker::onProcessSelected);
|
||||||
connect(ui->filterEdit, &QLineEdit::textChanged, this, &ProcessPicker::filterProcesses);
|
connect(ui->filterEdit, &QLineEdit::textChanged, this, &ProcessPicker::filterProcesses);
|
||||||
connect(ui->attachButton, &QPushButton::clicked, this, &ProcessPicker::onProcessSelected);
|
connect(ui->attachButton, &QPushButton::clicked, this, &ProcessPicker::onProcessSelected);
|
||||||
|
|
||||||
// Use custom process list
|
// Derive theme colors from the global palette (set by applyGlobalTheme)
|
||||||
m_allProcesses = customProcesses;
|
QPalette pal = qApp->palette();
|
||||||
applyFilter();
|
QString bg = pal.color(QPalette::Base).name();
|
||||||
|
QString text = pal.color(QPalette::Text).name();
|
||||||
|
QString hover = pal.color(QPalette::Mid).name();
|
||||||
|
QString surface = pal.color(QPalette::AlternateBase).name();
|
||||||
|
QString button = pal.color(QPalette::Button).name();
|
||||||
|
QString highlight= pal.color(QPalette::Highlight).name();
|
||||||
|
QString border = pal.color(QPalette::Mid).darker(120).name();
|
||||||
|
QString mutedText= pal.color(QPalette::Disabled, QPalette::WindowText).name();
|
||||||
|
QString hoverDk = pal.color(QPalette::Mid).darker(130).name();
|
||||||
|
|
||||||
|
ui->processTable->setStyleSheet(QStringLiteral(
|
||||||
|
"QTableWidget { background: %1; color: %2; border: none; }"
|
||||||
|
"QTableWidget::item { padding: 2px 6px; border: none; }"
|
||||||
|
"QTableWidget::item:hover { background: %3; padding: 2px 6px; border: none; }"
|
||||||
|
"QTableWidget::item:selected { background: %3; color: %2; padding: 2px 6px; border: none; }")
|
||||||
|
.arg(bg, text, hover));
|
||||||
|
|
||||||
|
ui->processTable->horizontalHeader()->setStyleSheet(QStringLiteral(
|
||||||
|
"QHeaderView::section { background: %1; color: %2; border: none;"
|
||||||
|
" padding: 4px 6px; text-align: left; }")
|
||||||
|
.arg(surface, text));
|
||||||
|
ui->processTable->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft | Qt::AlignVCenter);
|
||||||
|
|
||||||
|
ui->filterEdit->setStyleSheet(QStringLiteral(
|
||||||
|
"QLineEdit { background: %1; color: %2; border: 1px solid %3; padding: 2px 4px; }"
|
||||||
|
"QLineEdit:focus { border-color: %4; }")
|
||||||
|
.arg(bg, text, border, highlight));
|
||||||
|
|
||||||
|
QString btnStyle = QStringLiteral(
|
||||||
|
"QPushButton { background: %1; color: %2; border: 1px solid %3; padding: 4px 12px; }"
|
||||||
|
"QPushButton:hover { background: %4; }"
|
||||||
|
"QPushButton:pressed { background: %5; }"
|
||||||
|
"QPushButton:disabled { color: %6; }")
|
||||||
|
.arg(button, text, border, hover, hoverDk, mutedText);
|
||||||
|
ui->refreshButton->setStyleSheet(btnStyle);
|
||||||
|
ui->attachButton->setStyleSheet(btnStyle);
|
||||||
|
ui->cancelButton->setStyleSheet(btnStyle);
|
||||||
|
|
||||||
|
// Right-click context menu
|
||||||
|
ui->processTable->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||||
|
connect(ui->processTable, &QWidget::customContextMenuRequested, this, [this](const QPoint& pos) {
|
||||||
|
int row = ui->processTable->rowAt(pos.y());
|
||||||
|
if (row < 0) return;
|
||||||
|
auto* pidItem = ui->processTable->item(row, 0);
|
||||||
|
auto* nameItem = ui->processTable->item(row, 1);
|
||||||
|
auto* pathItem = ui->processTable->item(row, 2);
|
||||||
|
if (!pidItem || !nameItem) return;
|
||||||
|
|
||||||
|
QString pid = QString::number(pidItem->data(Qt::EditRole).toUInt());
|
||||||
|
QString name = nameItem->data(Qt::UserRole).toString();
|
||||||
|
QString path = pathItem ? pathItem->text() : QString();
|
||||||
|
|
||||||
|
QMenu menu;
|
||||||
|
auto* copyPid = menu.addAction(QStringLiteral("Copy PID"));
|
||||||
|
auto* copyName = menu.addAction(QStringLiteral("Copy Name"));
|
||||||
|
QAction* copyPath = nullptr;
|
||||||
|
if (!path.isEmpty())
|
||||||
|
copyPath = menu.addAction(QStringLiteral("Copy Path"));
|
||||||
|
|
||||||
|
auto* chosen = menu.exec(ui->processTable->viewport()->mapToGlobal(pos));
|
||||||
|
if (chosen == copyPid)
|
||||||
|
QApplication::clipboard()->setText(pid);
|
||||||
|
else if (chosen == copyName)
|
||||||
|
QApplication::clipboard()->setText(name);
|
||||||
|
else if (copyPath && chosen == copyPath)
|
||||||
|
QApplication::clipboard()->setText(path);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Auto-focus filter for immediate typing
|
||||||
|
ui->filterEdit->setFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
ProcessPicker::~ProcessPicker()
|
ProcessPicker::~ProcessPicker()
|
||||||
@@ -130,9 +200,6 @@ void ProcessPicker::enumerateProcesses()
|
|||||||
info.pid = pe32.th32ProcessID;
|
info.pid = pe32.th32ProcessID;
|
||||||
info.name = QString::fromWCharArray(pe32.szExeFile);
|
info.name = QString::fromWCharArray(pe32.szExeFile);
|
||||||
|
|
||||||
// Try to get full path and extract icon
|
|
||||||
// If we can't open a process with PROCESS_QUERY_LIMITED_INFORMATION then
|
|
||||||
// we for sure can't access their memory. - Skip in this case
|
|
||||||
HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pe32.th32ProcessID);
|
HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pe32.th32ProcessID);
|
||||||
if (hProcess)
|
if (hProcess)
|
||||||
{
|
{
|
||||||
@@ -292,3 +359,22 @@ void ProcessPicker::applyFilter()
|
|||||||
|
|
||||||
populateTable(filtered);
|
populateTable(filtered);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ProcessPicker::selectPreferredProcess()
|
||||||
|
{
|
||||||
|
// Try to select the last-attached process if it's in the list
|
||||||
|
QSettings s("Reclass", "Reclass");
|
||||||
|
QString lastProc = s.value("lastAttachedProcess").toString();
|
||||||
|
if (lastProc.isEmpty()) return;
|
||||||
|
|
||||||
|
for (int row = 0; row < ui->processTable->rowCount(); ++row) {
|
||||||
|
auto* nameItem = ui->processTable->item(row, 1);
|
||||||
|
if (!nameItem) continue;
|
||||||
|
QString name = nameItem->data(Qt::UserRole).toString();
|
||||||
|
if (name.compare(lastProc, Qt::CaseInsensitive) == 0) {
|
||||||
|
ui->processTable->selectRow(row);
|
||||||
|
ui->processTable->scrollToItem(nameItem);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -35,9 +35,11 @@ private slots:
|
|||||||
void filterProcesses(const QString& text);
|
void filterProcesses(const QString& text);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void initUi();
|
||||||
void enumerateProcesses();
|
void enumerateProcesses();
|
||||||
void populateTable(const QList<ProcessInfo>& processes);
|
void populateTable(const QList<ProcessInfo>& processes);
|
||||||
void applyFilter();
|
void applyFilter();
|
||||||
|
void selectPreferredProcess();
|
||||||
|
|
||||||
Ui::ProcessPicker *ui;
|
Ui::ProcessPicker *ui;
|
||||||
uint32_t m_selectedPid = 0;
|
uint32_t m_selectedPid = 0;
|
||||||
|
|||||||
@@ -127,22 +127,6 @@
|
|||||||
</widget>
|
</widget>
|
||||||
<resources/>
|
<resources/>
|
||||||
<connections>
|
<connections>
|
||||||
<connection>
|
|
||||||
<sender>attachButton</sender>
|
|
||||||
<signal>clicked()</signal>
|
|
||||||
<receiver>ProcessPicker</receiver>
|
|
||||||
<slot>accept()</slot>
|
|
||||||
<hints>
|
|
||||||
<hint type="sourcelabel">
|
|
||||||
<x>600</x>
|
|
||||||
<y>470</y>
|
|
||||||
</hint>
|
|
||||||
<hint type="destinationlabel">
|
|
||||||
<x>350</x>
|
|
||||||
<y>250</y>
|
|
||||||
</hint>
|
|
||||||
</hints>
|
|
||||||
</connection>
|
|
||||||
<connection>
|
<connection>
|
||||||
<sender>cancelButton</sender>
|
<sender>cancelButton</sender>
|
||||||
<signal>clicked()</signal>
|
<signal>clicked()</signal>
|
||||||
|
|||||||
Reference in New Issue
Block a user