Skip to content

Detachable pan adapters and radio panel windows like implemented in SmartSDR #246

Description

@ScooterTech

I've reviewed all 48 open issues. None of them cover pop-in/pop-out or detachable/floating panel windowing for panadapters or the Radio Panel. The closest issues are #152 (multi-panadapter support, which is about creating multiple pans) and the general GUI work in #56, but neither touches detachable/floating window behavior. No duplicate exists — here's your new issue:


GitHub Issue

Title: feat: Pop-out / pop-in and free-drag positioning for panadapters and Radio Panel (SmartSDR parity)


## What

Add the ability to **pop out** any panadapter panel or the Radio Panel (the
main VFO/applet strip) into its own independent, freely-positionable
top-level window, and **pop it back in** (dock it) to the main AetherSDR
window — mirroring SmartSDR's pop-out/pop-in behavior exactly.

While popped out, each panel:
- Becomes a standalone borderless (or thin-framed) `QWidget` window with
  its own title bar / drag handle
- Can be freely dragged to any screen position, including a second monitor
- Retains full live functionality (spectrum updates, click-to-tune, applet
  controls, etc.)
- Remembers its last screen position and restore state across restarts
  (persisted via `AppSettings`)

A **pop-in** button (or double-click on the drag handle) re-docks the panel
back to its original slot in the main window layout.

## Why

Multi-monitor ham radio operating is extremely common. Operators frequently
want:

- The **panadapter on one monitor**, the **VFO/applet controls on another**
- Multiple panadapters spread across monitors (especially relevant once
  issue #152 multi-pan lands)
- A compact "heads down" Radio Panel on a small secondary display while
  the spectrum occupies a large primary display

Currently, AetherSDR locks all panels into a single `QMainWindow` splitter.
This forces operators to choose between a large spectrum and enough room for
controls — there is no escape. SmartSDR for Windows solves this cleanly with
its pop-out system; AetherSDR needs parity.

## How Other Clients Do It

### SmartSDR for Windows (reference implementation)

SmartSDR uses a pop-out/pop-in model that is the direct target for this
feature:

- Every panadapter has a **pop-out icon** (↗ arrow) in its top-right corner.
  Clicking it detaches the panadapter into a standalone floating window.
- The **Radio Panel** (VFO strip + applets) has a corresponding pop-out
  control.
- Floating windows have a thin custom title bar with a **pop-in button**
  (↙ arrow) to re-dock.
- Windows can be dragged freely across monitors.
- Position is remembered per-radio, per-panadapter-ID across sessions.
- During pop-out, the slot in the main window either collapses or shows a
  placeholder/ghost.

### SDR# (SDRSharp)

Uses a **plugin panel docking** system where panels can be undocked from
the side rail into floating `Form` windows. Less polished than SmartSDR but
same concept.

### GQRX

Uses Qt's built-in `QDockWidget` for the control panels, which provides
free float + re-dock. The spectrum itself is not detachable in GQRX, but
the dock widget pattern is the standard Qt approach for this class of
feature.

## Suggested Behavior

### Pop-out trigger

- A small **↗ pop-out button** appears in the top-right corner of each
  `PanadapterApplet` header bar (and in the Radio Panel's drag/title zone).
- Keyboard shortcut: e.g. `Ctrl+Shift+P` for focused panadapter,
  `Ctrl+Shift+R` for Radio Panel (exact shortcuts TBD).

### Popped-out state

┌──────────────────────────────────────┐
│ ≡ Panadapter 1 [14.225 MHz / 40m] │ ↙ │ ✕ │ ← custom drag handle
├──────────────────────────────────────┤
│ │
│ [live SpectrumWidget] │
│ │
└──────────────────────────────────────┘


- The popped-out window is a `QWidget` with `Qt::Window | Qt::FramelessWindowHint`
  (or a thin custom frame).
- Custom drag handle at top: click-drag moves the window, ↙ button re-docks,
  ✕ closes (which should pop-in, not destroy the panadapter).
- The `SpectrumWidget` (or Radio Panel widget) is **re-parented**, not cloned —
  the same live widget moves into the floating window. No duplication of VITA-49
  routing or signal connections needed.

### Main window while panel is popped out

- The vacated slot in the `PanadapterStack` QSplitter collapses to zero height
  (or shows a thin placeholder strip with the pan name and a ↗ "re-open"
  button).
- The main window remains fully functional for all other panels.

### Pop-in (re-docking)

- Clicking ↙ in the floating window re-parents the widget back into the
  `PanadapterStack` at its original index.
- The placeholder strip in the main window expands back to the previous size.
- If the original slot no longer exists (pan was deleted while floating), the
  widget is destroyed gracefully.

### Persistence (`AppSettings`)

PanPopOut_0x40000000_IsFloating = True
PanPopOut_0x40000000_X = 1920
PanPopOut_0x40000000_Y = 100
PanPopOut_0x40000000_Width = 960
PanPopOut_0x40000000_Height = 600

RadioPanelPopOut_IsFloating = False


On launch, AetherSDR restores floating state before the first paint so there
is no visible "snap" of windows jumping around.

### Radio Panel pop-out

The same mechanism applies to the Radio Panel (the `MainWindow` center pane
containing `VfoWidget`, mode selector, `AppletPanel`, etc.). This lets the
operator move the entire control surface to a secondary monitor while the
spectrum stays on the primary.

## Protocol Hints

No FlexLib/SmartSDR protocol involvement — this is **purely a client-side
Qt windowing feature**. The radio is unaware of window positions.

The relevant Qt classes are:

| Class | Role |
|-------|------|
| `QWidget::setParent(nullptr)` + `show()` | Re-parent widget as top-level window |
| `QWidget::setParent(container)` + `show()` | Re-parent widget back into layout |
| `Qt::Window \| Qt::FramelessWindowHint` | Create borderless floating window |
| `QSplitter` | Already used in `PanadapterStack` — handles collapse on re-parent |
| `AppSettings` | Persist geometry and floating state |

> ⚠️ **Re-parenting caveat:** Qt re-parenting destroys and re-creates the
> native window handle. On X11/Wayland, OpenGL contexts (if any) must be
> re-initialized. `SpectrumWidget` currently uses `QPainter`/`QPixmap` — no
> OpenGL — so re-parenting should be safe. Verify this assumption before
> implementing.

> ⚠️ **Wayland note:** `Qt::FramelessWindowHint` behaves differently under
> Wayland compositors; window positioning via `move()` may be ignored. May
> need `QWindow::setPosition()` workaround or fallback to decorated windows
> on Wayland. Test on both X11 and Wayland (KDE/GNOME).

## Implementation Sketch

```cpp
// PanadapterApplet.h — add pop-out support
class PanadapterApplet : public QWidget {
    // ...
    QPushButton* m_popOutBtn{};
    QWidget*     m_floatingWindow{};   // nullptr when docked
    int          m_stackIndex{-1};     // slot in PanadapterStack

public slots:
    void popOut();
    void popIn();
};
void PanadapterApplet::popOut() {
    m_stackIndex = parentWidget() ? /* record splitter index */ : -1;
    setParent(nullptr);
    setWindowFlags(Qt::Window | Qt::FramelessWindowHint);

    auto& s = AppSettings::instance();
    int x = s.value("PanPopOut_" + m_panId + "_X", "100").toInt();
    int y = s.value("PanPopOut_" + m_panId + "_Y", "100").toInt();
    move(x, y);
    show();
    s.setValue("PanPopOut_" + m_panId + "_IsFloating", "True");
}

Labels

feature · GUI · spectrum · priority: medium


---

No duplicate found in the open issues — this is a fresh feature request ready to paste into GitHub. The only tangentially related open work is **#152** (multi-panadapter), so you may want to cross-link the two issues since pop-out becomes especially useful once multiple pans are available.

Metadata

Metadata

Assignees

No one assigned

    Labels

    GUIUser interfaceduplicateThis issue or pull request already existsenhancementImprovement to existing featuregood first issueGood for newcomershelp wantedExtra attention neededmaintainer-reviewRequires maintainer review before any action is takenpriority: lowLow priorityspectrumPanadapter and waterfall

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions