Skip to content
Open
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
114 changes: 109 additions & 5 deletions src/gui/DxClusterDialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
#include <QTableView>
#include <QHeaderView>
#include <QSortFilterProxyModel>
#include <QMenu>
#include <QAction>
#include <QSlider>
#include <QColorDialog>
#include <QFile>
Expand Down Expand Up @@ -55,6 +57,71 @@ class ResetOnDoubleClickSlider : public GuardedSlider {
int m_resetValue{0};
};

// Left-to-right layout that wraps to a new row when it runs out of
// horizontal space, rather than compressing children to illegibility
// (#4157). Used for the Spot List band-filter checkboxes so the
// checked state stays readable when SpotHub is narrow.
class FlowLayout : public QLayout {
public:
explicit FlowLayout(int margin, int hSpacing, int vSpacing)
: m_hSpace(hSpacing), m_vSpace(vSpacing) {
setContentsMargins(margin, margin, margin, margin);
}
~FlowLayout() override {
while (QLayoutItem* item = takeAt(0))
delete item;
}

void addItem(QLayoutItem* item) override { m_items.append(item); }
Qt::Orientations expandingDirections() const override { return {}; }
bool hasHeightForWidth() const override { return true; }
int heightForWidth(int width) const override { return doLayout(QRect(0, 0, width, 0), true); }
int count() const override { return m_items.size(); }
QLayoutItem* itemAt(int index) const override { return m_items.value(index); }
QLayoutItem* takeAt(int index) override {
return (index >= 0 && index < m_items.size()) ? m_items.takeAt(index) : nullptr;
}
QSize sizeHint() const override { return minimumSize(); }
QSize minimumSize() const override {
QSize size;
for (QLayoutItem* item : m_items)
size = size.expandedTo(item->minimumSize());
const QMargins m = contentsMargins();
return size + QSize(m.left() + m.right(), m.top() + m.bottom());
}
void setGeometry(const QRect& rect) override {
QLayout::setGeometry(rect);
doLayout(rect, false);
}

private:
int doLayout(const QRect& rect, bool testOnly) const {
const QMargins m = contentsMargins();
QRect effectiveRect = rect.adjusted(m.left(), m.top(), -m.right(), -m.bottom());
int x = effectiveRect.x();
int y = effectiveRect.y();
int lineHeight = 0;
for (QLayoutItem* item : m_items) {
int nextX = x + item->sizeHint().width() + m_hSpace;
if (nextX - m_hSpace > effectiveRect.right() && lineHeight > 0) {
x = effectiveRect.x();
y += lineHeight + m_vSpace;
nextX = x + item->sizeHint().width() + m_hSpace;
lineHeight = 0;
}
if (!testOnly)
item->setGeometry(QRect(QPoint(x, y), item->sizeHint()));
x = nextX;
lineHeight = qMax(lineHeight, item->sizeHint().height());
}
return y + lineHeight - rect.y() + m.bottom();
}

QList<QLayoutItem*> m_items;
int m_hSpace;
int m_vSpace;
};

// Shared DSP-style toggle for every checkable button in SpotHub
// (matches kDspToggle in VfoWidget.cpp so the chrome reads the same as
// the NB / NR / ANF buttons in the VFO panel). Dark inset by default,
Expand Down Expand Up @@ -274,7 +341,11 @@ DxClusterDialog::DxClusterDialog(DxClusterClient* clusterClient, DxClusterClient
m_radioModel(radioModel), m_dxccProvider(dxccProvider)
{
theme::setContainer(this, QStringLiteral("dialog/dxCluster"));
setMinimumSize(680, 560);
// Width floor lowered from 680 (#4157) so SpotHub can be dragged down
// toward the Spot List tab's visible-column width once columns are
// hidden; the other tabs' own layouts still enforce their own wider
// minimums via Qt's normal layout-constraint sizing.
setMinimumSize(360, 560);
resize(760, 640);

// Capture source log paths up front so the per-tab Clear handlers (built
Expand Down Expand Up @@ -1857,9 +1928,9 @@ void DxClusterDialog::buildSpotListTab(QTabWidget* tabs)
auto* layout = new QVBoxLayout(page);
layout->setSpacing(4);

// Band filter checkboxes
auto* filterRow = new QHBoxLayout;
filterRow->setSpacing(2);
// Band filter checkboxes: wraps to additional rows instead of being
// compressed unreadable when SpotHub is narrow (#4157).
auto* filterRow = new FlowLayout(0, 8, 4);
auto* filterLabel = new QLabel("Bands:");
AetherSDR::ThemeManager::instance().applyStyleSheet(filterLabel, "QLabel { color: {{color.text.label}}; font-size: 13px; }");
filterRow->addWidget(filterLabel);
Expand Down Expand Up @@ -1891,7 +1962,7 @@ void DxClusterDialog::buildSpotListTab(QTabWidget* tabs)
s.setValue(key, on ? "True" : "False");
s.save();
});
filterRow->addWidget(cb, 1); // equal stretch across row
filterRow->addWidget(cb);
}
layout->addLayout(filterRow);

Expand Down Expand Up @@ -1938,6 +2009,39 @@ void DxClusterDialog::buildSpotListTab(QTabWidget* tabs)
// No default sort — insertion order is newest-first
m_spotTable->horizontalHeader()->setSortIndicatorShown(false);

// Column visibility (#4157): Time/Freq/DX Call stay always-on as the
// core columns; the rest can be hidden via a right-click header menu,
// with the hidden set persisted in AppSettings across dialog reopen.
static const struct { SpotTableModel::Column col; const char* key; } kToggleCols[] = {
{ SpotTableModel::ColComment, "SpotListColVisible_Comment" },
{ SpotTableModel::ColSpotter, "SpotListColVisible_Spotter" },
{ SpotTableModel::ColBand, "SpotListColVisible_Band" },
{ SpotTableModel::ColMode, "SpotListColVisible_Mode" },
{ SpotTableModel::ColSource, "SpotListColVisible_Source" },
Comment on lines +2016 to +2020

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per Principle V (see the PR template checklist and .specify/memory/constitution.md), new features should persist as a single nested-JSON value under one root key rather than adding flat keys — here that's 5 new top-level keys (SpotListColVisible_Comment, _Spotter, …). Something like a single SpotListColumnVisibility key holding {"Comment": true, "Spotter": false, …} would keep the settings surface tidy and match the convention for new persistence.

That said, the entire DxClusterDialog (and the adjacent band-filter code you touched) already uses flat keys, so a lone nested key here would be locally inconsistent. Reasonable to either follow Principle V for the new feature or deliberately match the surrounding flat style — flagging so it's a conscious choice rather than an oversight.

};
auto& colVisSettings = AppSettings::instance();
for (const auto& tc : kToggleCols) {
bool visible = colVisSettings.value(tc.key, "True").toString() == "True";
m_spotTable->setColumnHidden(tc.col, !visible);
}
m_spotTable->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu);
connect(m_spotTable->horizontalHeader(), &QWidget::customContextMenuRequested, this,
[this](const QPoint& pos) {
QMenu menu(this);
for (const auto& tc : kToggleCols) {
auto* action = menu.addAction(m_spotModel->headerData(tc.col, Qt::Horizontal, Qt::DisplayRole).toString());
action->setCheckable(true);
action->setChecked(!m_spotTable->isColumnHidden(tc.col));
connect(action, &QAction::toggled, this, [this, tc](bool on) {
m_spotTable->setColumnHidden(tc.col, !on);
auto& s = AppSettings::instance();
s.setValue(tc.key, on ? "True" : "False");
s.save();
});
}
menu.exec(m_spotTable->horizontalHeader()->mapToGlobal(pos));
});

// Double-click to tune (#2298: also forward mode hints so the receiver
// switches CW/SSB to match the spot rather than only changing frequency).
connect(m_spotTable, &QTableView::doubleClicked, this, [this](const QModelIndex& idx) {
Expand Down
Loading