js8call/JS8_Mainwindow/ensureMessageDock.cpp

184 lines
8.2 KiB
C++
Raw Permalink Normal View History

/** \file
* @brief UI_Constructor::ensureMessageDock()
* member function of the UI_Constructor class
*
* Lazily constructs the dockable Message Inbox panel (messageDock_ /
* messagePanel_) on first use, including its custom toolbar (callsign
* filter box, clear/float/close buttons) and the signal connections that
* keep the "Show Message Inbox" menu action, dock visibility, and floating
* window behavior in sync.
*
* @note Must be called before restoreState() in readSettings() so the
* toolbar exists before saved dock visibility is restored.
*/
#include "JS8_UI/mainwindow.h"
void UI_Constructor::ensureMessageDock()
{
if (messageDock_) return;
messageDock_ = new QDockWidget(tr("Message Inbox"), this);
messageDock_->setObjectName("messageInboxDock"); // important for save/restoreState
// Choose where it can dock:
messageDock_->setAllowedAreas(Qt::LeftDockWidgetArea |
Qt::RightDockWidgetArea |
Qt::BottomDockWidgetArea);
// Choose behavior:
messageDock_->setFeatures(QDockWidget::DockWidgetMovable |
QDockWidget::DockWidgetFloatable |
QDockWidget::DockWidgetClosable);
// Initial placement:
addDockWidget(Qt::RightDockWidgetArea, messageDock_);
// Optional: closing hides (default); ensure no auto-delete:
messageDock_->setAttribute(Qt::WA_DeleteOnClose, false);
// --- Build the custom toolbar ---
messagePanel_ = new MessagePanel(inboxPath(), this);
// Container that holds the filter bar + the panel, becomes the dock's content
auto *container = new QWidget(messageDock_);
auto *vlay = new QVBoxLayout(container);
vlay->setContentsMargins(0, 0, 0, 0);
vlay->setSpacing(0);
auto *filterBar = new QWidget(container);
auto *h = new QHBoxLayout(filterBar);
h->setContentsMargins(6, 2, 6, 2);
auto *lbl = new QLabel("Inbox:", filterBar);
auto *edit = new QLineEdit(filterBar);
edit->setPlaceholderText("Filter by callsign");
{
const int chars = 10;
const QFontMetrics fm(edit->font());
const int textWidth = fm.horizontalAdvance(QString(chars, QChar('W')));
edit->setMaximumWidth(textWidth + 10);
edit->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
}
auto *clearBtn = new QToolButton(filterBar);
clearBtn->setText("<-clear");
clearBtn->setToolTip("Clear filter");
clearBtn->setAutoRaise(true);
h->addStretch();
h->addWidget(lbl);
h->addWidget(edit);
h->addWidget(clearBtn);
filterBar->setLayout(h);
vlay->addWidget(filterBar);
vlay->addWidget(messagePanel_);
container->setLayout(vlay);
messageDock_->setWidget(container);
m_inboxFilterEdit_ = edit;
connect(edit, &QLineEdit::textChanged, this, [this](const QString &txt) {
if (messagePanel_) messagePanel_->setCall(txt.trimmed().isEmpty() ? "%" : txt.trimmed().toUpper());
});
connect(clearBtn, &QToolButton::clicked, this, [this]() {
if (m_inboxFilterEdit_) m_inboxFilterEdit_->clear();
});
// Make the menu action reflect visibility automatically:
ui->actionShow_Message_Inbox->setCheckable(true);
ui->actionShow_Message_Inbox->setChecked(messageDock_->isVisible());
connect(messageDock_, &QDockWidget::visibilityChanged, this,
[this](bool visible) {
QSignalBlocker b(ui->actionShow_Message_Inbox);
ui->actionShow_Message_Inbox->setChecked(visible);
});
/**
* @brief Promote the floating Message Inbox dock to a real top-level window.
* Message Inbox enhancements by Chris-AC9KH, August 2026
*
* By default QDockWidget floats as a Qt::Tool-style panel. Reassigning
* Qt::Window here gives it normal window chrome (minimize/maximize/close)
* and lets it behave like an independent window rather than a utility
* panel tied to MainWindow.
*
* @note Platform / windowing-system behavior differs significantly:
*
* - MacOS: works fully. Native titlebar with working minimize, and the
* floating dock can be sent behind the main window by clicking on
* it, like any ordinary Cocoa window. Drag and double-click to
* redock both work from the titlebar.
*
* - Windows: window chrome (min/max/close) renders correctly and
* drag/double-click redock work, but the floating dock can NOT be
* sent behind the main window by clicking it. This is a platform
* limitation, not a bug: the dock remains a Qt-parented child of
* MainWindow, and Windows always keeps an owned window above its
* owner in z-order regardless of the Qt window flags set here.
*
* - Linux / X11 (confirmed on Linux Mint, XFCE): works partially.
* Clicking the main window correctly sends the floating dock
* behind it, and a taskbar icon is available to bring it back.
* Drag and double-click redock both work, however X11 treats the
* titlebar as a system window so double clicking maximizes the message
* panel. Drag-to-dock and double-click-to-dock works in the secondary
* Qt widget area under the titlebar.
*
* - Linux / Wayland (confirmed on Ubuntu 26/GNOME/Wayland): the
* floating dock cannot be sent to the background clicking the
* main window has no effect, and it always stays in the
* foreground, the same practical symptom as Windows though via a
* different mechanism (Wayland's own compositor stacking policy
* rather than Windows' owned-window z-order rule). In addition, the
* dock cannot be dragged while floating, and always appears
* re-centered on screen rather than at its prior position, because
* Wayland does not allow clients to set or query their own
* top-level window position. The compositor alone controls window
* placement, and interactive window moves require a client to
* issue an xdg_toplevel::move protocol request on pointer-down,
* which Qt's internal dock-widget drag logic does not do.
* Double-click-to-dock still works, since it's a discrete state
* change rather than a continuous position update. Detected at
* runtime via QGuiApplication::platformName() so this only affects
* genuine Wayland sessions, not Xwayland/X11 sessions on the same
* linux distribution.
*
* @warning Two fixes were evaluated and deliberately rejected:
* - Calling setParent(nullptr) on messageDock_ when floating, to
* escape the owned-window z-order rule on Windows. Removing the Qt
* parent breaks QMainWindowLayout's tracking of the dock, which
* broke docking, dragging, and closing entirely. Not viable.
* - Implementing the Wayland xdg_toplevel::move protocol request
* directly to restore floating-window drag under Wayland. This is
* real compositor-protocol-level work, not a Qt-level fix, and was
* judged not worth the added complexity for what the Wayland
* project itself may address in its own compositor implementations
* over time.
*/
connect(messageDock_, &QDockWidget::topLevelChanged, this, [this](bool floating) {
if (floating) {
const bool isWayland = QGuiApplication::platformName().startsWith("wayland");
if (!isWayland) {
messageDock_->setWindowFlags(Qt::Window
| Qt::WindowCloseButtonHint
| Qt::WindowMinMaxButtonsHint);
messageDock_->show();
}
}
});
// Handle reply function
connect(messagePanel_, &MessagePanel::replyMessage, this,
[this](const QString &text) {
addMessageText(text, true, true);
refreshInboxCounts();
displayCallActivity();
});
connect(messagePanel_, &MessagePanel::countsUpdated, this, [this]() {
refreshInboxCounts();
displayCallActivity();
});
}