Persist shared chat media geometry and the physical monitor per profile
This commit is contained in:
1 parent
1d9570e38b
commit
2a0374e2ae
2 files changed
+121
-7
No files matched your search
@@ -51,6 +51,7 @@ extern "C" {
|
||||
#include <QElapsedTimer>
|
||||
#include <QApplication>
|
||||
#include <QScreen>
|
||||
#include <QWindow>
|
||||
#include <QClipboard>
|
||||
#include <QStyle>
|
||||
#include <QMessageBox>
|
||||
@@ -199,6 +200,8 @@ MainWindow::MainWindow(QWidget *parent, DbManager* db, const QString& cfgPath,
|
||||
}
|
||||
|
||||
MainWindow::~MainWindow() {
|
||||
delete m_videoPlayer.data();
|
||||
delete m_imageViewer.data();
|
||||
gui_bridge_set_radio_call_cb(nullptr);
|
||||
if (m_radioCallWindow) { m_radioCallId = 0; delete m_radioCallWindow.data(); }
|
||||
if (m_streamPublishWindow) delete m_streamPublishWindow.data();
|
||||
@@ -324,6 +327,98 @@ void MainWindow::restoreWindowGeometry() {
|
||||
}
|
||||
}
|
||||
|
||||
/* Один снимок на профиль; кэш обновляется до асинхронной записи, поэтому быстрое переоткрытие видит новую геометрию. */
|
||||
void MainWindow::saveMediaWindowGeometry(QWidget* window) {
|
||||
const QRect normal = window->normalGeometry();
|
||||
const QScreen* display = window->screen();
|
||||
if (!normal.isValid() || !display) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "MediaWindow: cannot save geometry window=%s valid=%d screen=%d",
|
||||
qPrintable(window->objectName()), normal.isValid(), display != nullptr);
|
||||
return;
|
||||
}
|
||||
const QString mode = window->isFullScreen() ? "fullscreen" : window->isMaximized() ? "maximized" : "normal";
|
||||
m_mediaWindowState = {{"x", normal.x()}, {"y", normal.y()}, {"width", normal.width()}, {"height", normal.height()},
|
||||
{"mode", mode}, {"screen", display->name()}, {"serial", display->serialNumber()},
|
||||
{"screen_x", display->availableGeometry().x()}, {"screen_y", display->availableGeometry().y()}};
|
||||
m_mediaWindowStateLoaded = true;
|
||||
const QByteArray value = QJsonDocument(m_mediaWindowState).toJson(QJsonDocument::Compact);
|
||||
const int queued = gui_bridge_post_setting(GUI_SETTING_UI_STATE, "media_window_geometry", value.constData());
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "MediaWindow: cached %s rect=%d,%d %dx%d mode=%s monitor=%s serial=%s queued=%d",
|
||||
qPrintable(window->objectName()), normal.x(), normal.y(), normal.width(), normal.height(), qPrintable(mode),
|
||||
qPrintable(display->name()), qPrintable(display->serialNumber()), queued == 0);
|
||||
if (queued < 0) DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "MediaWindow: geometry remains cached; database write was not queued");
|
||||
}
|
||||
|
||||
/* Имя/serial определяют физический монитор; относительное смещение сохраняется и при перестановке мониторов. */
|
||||
void MainWindow::restoreMediaWindowGeometry(QWidget* window) {
|
||||
if (!m_mediaWindowStateLoaded && m_db && m_db->isOpen()) {
|
||||
m_mediaWindowStateLoaded = true;
|
||||
const QByteArray value = m_db->getUiState("media_window_geometry").toUtf8();
|
||||
if (!value.isEmpty()) {
|
||||
QJsonParseError error;
|
||||
const auto document = QJsonDocument::fromJson(value, &error);
|
||||
if (error.error != QJsonParseError::NoError || !document.isObject())
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "MediaWindow: invalid saved state bytes=%d error=%s", int(value.size()),
|
||||
qPrintable(error.errorString()));
|
||||
else m_mediaWindowState = document.object();
|
||||
}
|
||||
}
|
||||
const QString mode = m_mediaWindowState.value("mode").toString();
|
||||
const int savedWidth = m_mediaWindowState.value("width").toInt();
|
||||
const int savedHeight = m_mediaWindowState.value("height").toInt();
|
||||
const bool saved = savedWidth > 0 && savedHeight > 0 && (mode == "normal" || mode == "maximized" || mode == "fullscreen");
|
||||
if (!m_mediaWindowState.isEmpty() && !saved)
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "MediaWindow: invalid saved geometry width=%d height=%d mode=%s",
|
||||
savedWidth, savedHeight, qPrintable(mode));
|
||||
const QString name = m_mediaWindowState.value("screen").toString();
|
||||
const QString serial = m_mediaWindowState.value("serial").toString();
|
||||
QScreen* display = nullptr;
|
||||
if (saved) {
|
||||
for (auto* candidate : QGuiApplication::screens()) {
|
||||
if (candidate->name() == name && (serial.isEmpty() || candidate->serialNumber() == serial)) {
|
||||
display = candidate; break;
|
||||
}
|
||||
}
|
||||
if (!display && !serial.isEmpty()) {
|
||||
for (auto* candidate : QGuiApplication::screens()) {
|
||||
if (candidate->serialNumber() == serial) { display = candidate; break; }
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!display) {
|
||||
display = screen() ? screen() : QGuiApplication::primaryScreen();
|
||||
if (saved) DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "MediaWindow: saved monitor unavailable name=%s serial=%s fallback=%s",
|
||||
qPrintable(name), qPrintable(serial), display ? qPrintable(display->name()) : "none");
|
||||
}
|
||||
if (!display) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "MediaWindow: no physical screen available for restoration"); return; }
|
||||
const QRect available = display->availableGeometry();
|
||||
if (!available.isValid()) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "MediaWindow: monitor has no available area name=%s", qPrintable(display->name())); return;
|
||||
}
|
||||
window->setMinimumSize(window->minimumSize().boundedTo(available.size()));
|
||||
const QSize size = QSize(saved ? savedWidth : 800, saved ? savedHeight : 600)
|
||||
.expandedTo(window->minimumSize()).boundedTo(available.size());
|
||||
qint64 x = available.x() + (available.width() - size.width()) / 2;
|
||||
qint64 y = available.y() + (available.height() - size.height()) / 2;
|
||||
if (saved) {
|
||||
x = qint64(m_mediaWindowState.value("x").toInt()) - m_mediaWindowState.value("screen_x").toInt() + available.x();
|
||||
y = qint64(m_mediaWindowState.value("y").toInt()) - m_mediaWindowState.value("screen_y").toInt() + available.y();
|
||||
}
|
||||
const QRect target(int(qBound<qint64>(available.left(), x, available.right() - size.width() + 1)),
|
||||
int(qBound<qint64>(available.top(), y, available.bottom() - size.height() + 1)), size.width(), size.height());
|
||||
if (saved && (target.x() != x || target.y() != y || size != QSize(savedWidth, savedHeight)))
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "MediaWindow: fitted saved rect into monitor=%s area=%d,%d %dx%d",
|
||||
qPrintable(display->name()), available.x(), available.y(), available.width(), available.height());
|
||||
window->winId();
|
||||
window->windowHandle()->setScreen(display);
|
||||
window->setGeometry(target);
|
||||
window->setWindowState(!saved || mode == "normal" ? Qt::WindowNoState
|
||||
: mode == "fullscreen" ? Qt::WindowFullScreen : Qt::WindowMaximized);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "MediaWindow: restored %s saved=%d rect=%d,%d %dx%d mode=%s monitor=%s serial=%s",
|
||||
qPrintable(window->objectName()), saved, target.x(), target.y(), target.width(), target.height(),
|
||||
saved ? qPrintable(mode) : "normal", qPrintable(display->name()), qPrintable(display->serialNumber()));
|
||||
}
|
||||
|
||||
void MainWindow::setupNode() {
|
||||
m_node = new UtunNode(this);
|
||||
connect(m_node, &UtunNode::error, this, [](const QString& msg) {
|
||||
@@ -1274,6 +1369,10 @@ void MainWindow::requestShutdown() {
|
||||
}
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "MainWindow: requestShutdown — saving geometry and stopping node");
|
||||
if (m_messageNotification) m_messageNotification->dismiss("shutdown");
|
||||
QPointer<QWidget> first = m_imageViewer.data(), last = m_videoPlayer.data();
|
||||
if (first && first->isActiveWindow()) std::swap(first, last); // Активный просмотр закрывается последним и задаёт общий снимок.
|
||||
if (first) first->close();
|
||||
if (last) last->close();
|
||||
saveWindowGeometry();
|
||||
if (m_messageList) m_messageList->saveChannelState();
|
||||
// FIFO: stop исполняется после уже принятых записей UI. Node живёт до join его worker.
|
||||
@@ -1353,9 +1452,13 @@ void MainWindow::showSettings() {
|
||||
void MainWindow::openVideo(const QString& filePath) {
|
||||
if (!m_videoPlayer) {
|
||||
m_videoPlayer = new VideoPlayerWindow(nullptr);
|
||||
connect(m_videoPlayer, &VideoPlayerWindow::closed, this, [this] {
|
||||
m_videoPlayer->deleteLater();
|
||||
m_videoPlayer->setObjectName(QStringLiteral("chatVideoPlayer"));
|
||||
restoreMediaWindowGeometry(m_videoPlayer);
|
||||
auto* player = m_videoPlayer.data();
|
||||
connect(player, &VideoPlayerWindow::closed, this, [this, player] {
|
||||
saveMediaWindowGeometry(player);
|
||||
m_videoPlayer = nullptr;
|
||||
player->deleteLater();
|
||||
});
|
||||
} else {
|
||||
m_videoPlayer->raise();
|
||||
@@ -1367,16 +1470,20 @@ void MainWindow::openVideo(const QString& filePath) {
|
||||
void MainWindow::openImage(const QString& filePath) {
|
||||
if (!m_imageViewer) {
|
||||
m_imageViewer = new ImageViewerWindow(nullptr);
|
||||
connect(m_imageViewer, &ImageViewerWindow::closed, this, [this] {
|
||||
m_imageViewer->deleteLater();
|
||||
m_imageViewer->setObjectName(QStringLiteral("chatImageViewer"));
|
||||
restoreMediaWindowGeometry(m_imageViewer);
|
||||
auto* viewer = m_imageViewer.data();
|
||||
connect(viewer, &ImageViewerWindow::closed, this, [this, viewer] {
|
||||
saveMediaWindowGeometry(viewer);
|
||||
m_imageViewer = nullptr;
|
||||
viewer->deleteLater();
|
||||
});
|
||||
} else {
|
||||
m_imageViewer->raise();
|
||||
m_imageViewer->activateWindow();
|
||||
}
|
||||
m_imageViewer->loadFile(filePath);
|
||||
m_imageViewer->showFullScreen();
|
||||
m_imageViewer->show();
|
||||
}
|
||||
|
||||
void MainWindow::onMessageReceived(const char* ch_id, int ch_id_len, uint64_t author, bool initialSync) {
|
||||
|
||||
Reference in new issue
Block a user