From 17772163cde7137e2dfd017ff72315499f075d41 Mon Sep 17 00:00:00 2001 From: evgeny Date: Wed, 28 Oct 2026 00:15:41 +0100 Subject: [PATCH] Complete MainWindow and MessageList contracts for commands and snapshots --- tools/chatgui/src/mainwindow.h | 39 +++++++++++++++++++++++++++++++++ tools/chatgui/src/messagelist.h | 29 ++++++++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/tools/chatgui/src/mainwindow.h b/tools/chatgui/src/mainwindow.h index e8f6a778..7f010179 100644 --- a/tools/chatgui/src/mainwindow.h +++ b/tools/chatgui/src/mainwindow.h @@ -41,14 +41,17 @@ struct AutoPlayItem { class MainWindow : public QMainWindow { Q_OBJECT public: + // db заимствован до деструктора; nullptr создаёт UI без UtunNode. cfgPath — профиль, dbPath — override каталога. explicit MainWindow(QWidget *parent, DbManager* db, const QString& cfgPath, const QString& dbPath = QString(), const QString& debugFile = QString(), const QString& debugLevel = QString(), const QString& debugCategories = QString()); + // Снять callbacks, остановить media, stop/join node и очистить анимации. ~MainWindow() override; protected: + // Сохранить геометрию и скрыть в tray; для выхода служит requestShutdown. void closeEvent(QCloseEvent *event) override; private slots: @@ -61,56 +64,92 @@ private slots: void openVideo(const QString& filePath); void openImage(const QString& filePath); void showChannelSettings(const QString& channelId); + // Дождаться конца/подготовки player, разрешить путь головы и запустить один voice. void autoPlayTimerTick(); public: + // ch_id задан с длиной; initialSync обновляет историю без уведомления, звука и auto-play. void onMessageReceived(const char* ch_id, int ch_id_len, uint64_t author, bool initialSync); + // Передать бинарный CHAT_MEMBER_DISPLAY снимок списку участников и кэшу аватаров. void onMemberList(const QString& chId, int count, const uint8_t* data, int dataLen); + // Обновить участника по codec chat_member.h; байты заимствованы до возврата. void onMemberUpdated(const QString& chId, const uint8_t* data); + // Удалить nodeId из модели/кэша выбранного chId. void onMemberRemoved(const QString& chId, uint64_t nodeId); + // Обновить статус поиска соседей канала. void onAutoConnectStatus(uint8_t status, uint16_t total, uint16_t totalNodes, uint16_t connected); + // Обновить online-счётчик ch_id указанной длины. void onChannelPeersOnline(const char* ch_id, int ch_id_len, uint16_t online); + // Перечитать список, сбросить исчезнувший target и применить отложенный выбор. void reloadChannels(); + // Завершение (channel,rowid): разбудить очередь, проверить готовый encrypted voice. void onAttachmentDownloadedForAutoPlay(const QString& chId, int64_t msgId); + // Сохранить geometry/read/scroll и поставить stop после этих команд; OOM допускает прямой stop с логом. void requestShutdown(); + // Заимствованный QObject-владелец ядра либо nullptr; управление — в GUI. UtunNode* utunNode() const { return m_node; } /* звонки */ + // Создать исходящее окно после принятия START мостом; chId может быть dm:. void onCallRequested(const QString& chId, quint64 nodeId, const QString& name); + // Показать входящий callId с именем caller из DbManager; повтор ID не создаёт окно. void onCallIncoming(const QString& chId, quint64 caller, quint64 callId); + // Обновить состояние существующего исходящего окна и тон. void onCallRinging(quint64 callId); + // Активировать callId/audio; при отказе capture послать hangup. void onCallAccepted(quint64 callId); + // Завершить audio/окно, при PHONE_BUSY показать причину. void onCallEnded(quint64 callId, uint8_t reason); + // Залогировать протокольную ошибку и закрыть окно callId. void onCallError(quint64 callId, uint8_t err); + // Обновить диагностический BGP-путь существующего окна. void onCallPath(quint64 callId, const QString& path); + // Снять callId из map до deleteLater; повтор безопасен. void closeCallWindow(quint64 callId); + // Передать START для CHAT-канала и peer с новым ненулевым ID. void onRadioCallRequested(const QString& channel, quint64 peer); + // Применить 27-байтный radio_call event, управлять диалогом/рингтоном и READY/ACCEPT. void onRadioCallEvent(const uint8_t* data, int len); /* DM (p2p чат) */ + // Скопировать общую группу/peer/имя в команду; PM выбирается после DM_CONV_UPDATED. void onPmRequested(const QString& chId, quint64 nodeId, const QString& name); + // Открыть отправку свежего invite фиксированному peer; source может быть пустым из PM. void onInviteToPmRequested(const QString& source, quint64 peer, const QString& name); + // Разобрать utun/utun-msg ссылку и перейти к доступной группе/оригиналу. void onMessageLinkActivated(const QString& link); + // Выбрать только видимое совпавшее поколение истории PM. void onDmConvUpdated(const QString& convId, quint64 generation); + // Обновить PM/уведомление; выбранной беседе запросить снимок. void onDmMsgReceived(const QString& convId, quint64 author); + // Подтвердить удаление конкретного generation и заблокировать его composer. void onDmDeleteRequested(const QString& convId, quint64 generation, const QString& name); + // Применить только зарегистрированный request/conv/generation без очистки новой истории. void onDmDeleteResult(quint64 request, const QString& convId, quint64 generation, bool success); private: void setupUi(); + // Поставить owned geometry/maximized/splitter в ui_state. void saveWindowGeometry(); + // Восстановить размеры в пределах доступных экранов. void restoreWindowGeometry(); void setupTray(); void showIncomingMessageNotification(const QString& target, quint64 author); + // Запустить UtunNode; stopping отсоединяет SQLite до native teardown. void setupNode(); void setupMessaging(); void setupSoundFromConfig(); + // Один MainWindow получает bridge events в GUI; деструктор снимает callbacks. void setupBridgeCallbacks(); + // Связать списки/composer/окна с командами и локальными действиями. void setupConnects(); void onShutdownCheck(); + // Проверить последнюю запись или msgId>0; повторный/свой voice не добавлять. void autoPlayCheckNewMessage(const QString& channelId, qint64 msgId = 0); QString autoPlayMediaDir() const; + // Путь обычного voice/проверенного plaintext VOICE; неизвестный encrypted MIME даёт пусто. QString autoPlayFilePath(const QString& channelId, const MessageRow& message) const; + // Открыть CHAT browser с owner/общим списком; DM не поддерживается. void showMemberFiles(const QString& channel, quint64 owner = 0); void showStreamPublisher(quint64 group, StreamGridWindow* returnTo = nullptr); // Управление; закрытие возвращает фокус сетке. diff --git a/tools/chatgui/src/messagelist.h b/tools/chatgui/src/messagelist.h index d5d72015..7ab125da 100644 --- a/tools/chatgui/src/messagelist.h +++ b/tools/chatgui/src/messagelist.h @@ -29,33 +29,54 @@ struct MessageRow; class MessageList : public QWidget { Q_OBJECT public: + // db/recorder заимствованы; Qt parent владеет виджетом. nullptr db допустим для пустого UI. explicit MessageList(DbManager* db = nullptr, AudioRecorder* recorder = nullptr, QWidget *parent = nullptr); + // Сохранить канал, сменить target; для dm: зафиксировать generation и запросить снимок. void loadChannel(const QString& channelId); + // Добавить отсутствующие сообщения (до 200); не вытеснять историческое окно цитаты. void refresh(); + // Перечитать имена, обновить авторов и превью цитат. void reloadLocalNicks(); + // Поставить read/верхнюю позицию в ui_state; окно старой цитаты сохраняет свежий watermark. void saveChannelState(); + // После остановки AnimTimer удалить общие LottieIcon; не вызывать при их отрисовке. static void cleanupAnimations(); + // Обновить только (chId,msgId) текущей модели без сетевого запроса. void updateMessageProgress(const QString& chId, int64_t msgId, int blocksDone, int numBlocks); + // Перечитать путь/роли; отложенный пользовательский play требует прежний target. void onAttachmentDownloaded(const QString& chId, int64_t msgId); + // [conv_len,conv,generation,JSON]: чужое/старое поколение не применяется. void onDmMessages(const uint8_t* data, int len); /* Блокировать отправки ожидающего удаления; forget очищает UI только для удалённого поколения и возвращает true. */ void setDmDeleting(const QString& convId, quint64 generation, bool deleting); bool forgetDmHistory(const QString& convId, quint64 generation); + // Запросить адресата/общую группу; отказ очищает доступность кнопки. void refreshDmCallRoute(); + // [conv_len,conv,peer,group] только для выбранного PM; group=0 отключает звонок. void onDmCallRoute(const uint8_t* data, int len); + // Сбросить peer/group и показать offline. void clearDmCallRoute(); + // Проверить codec и передать target/job/state composer. void onAttachmentPreparation(const uint8_t* data, int len); + // Запросить свежие реакции/опрос для изменённого сообщения текущего канала. void onFeedbackChanged(const uint8_t* data, int len); + // Проверить результат/JSON-снимок; чужой канал не меняет модель. void onFeedbackResult(const uint8_t* data, int len); + // 9 байт (request_id LE,success) подтверждают/отклоняют зафиксированный черновик. void onMessageSubmitted(const uint8_t* data, int len); + // Применить новую версию подписанного item с сохранением scroll и превью ответов. void onMessageUpdated(const uint8_t* data, int len); + // Обновить encrypted-вложение и диалог выдачи ключей текущего канала. void onAttachmentKeysChanged(const uint8_t* data, int len); void onRadioTalk(quint64 groupId, quint64 srcNodeId, bool on); /* индикация говорящего */ bool enableRadio(); /* идемпотентное включение рации выбранного канала, false при ошибке аудио */ + // Применить проверенный полный каталог выбранной CHAT-группы. void onStreamSources(const uint8_t* data, int len); + // Очистить каталог/кнопку remote streams. void clearStreams(); + // Наблюдать CHAT-группу; пустой target/PM передаёт 0 и снимает наблюдение. void watchStreams(); /* Показывает кнопку своей публикации во всех каналах; group=0 скрывает после завершения. GUI-поток. */ void setStreamPublisherGroup(quint64 group); @@ -121,11 +142,14 @@ private: QByteArray m_historyAnchor; void updateReply(QStandardItem* item, const QByteArray& original); void refreshReplies(); + // Разрешить подписанный ID в выбранном канале, загрузить окно и подсветить оригинал. void jumpToMessage(const QString& messageId); bool sendFeedback(QJsonObject command, bool userAction = true); // true — запрос принят; ch фиксируется до модального UI. void setupFeedback(QStandardItem* item, const MessageRow& message, const QString& channel); + // Проверить UTF-8 лимиты и передать опрос в исходный канал диалога. void createPoll(); + // Показать один диалог опроса или запросить недостающий снимок. void showPoll(const QString& messageId); void applyFeedback(const QJsonObject& state); void requestReaction(const QModelIndex& index, int emoji); @@ -133,10 +157,15 @@ private: QString authorName(quint64 nodeId) const; void restoreScrollPosition(const QString& channelId); + // Максимальный rowid полностью видимых строк; watermark меняется после принятия записи. void updateReadPosition(); + // Persistent index/QPointer защищают menu от замены модели/уничтожения UI. void onMessageContextMenu(const QPoint& pos); + // Запросить выбранное поколение PM; ожидающее удаление блокирует запрос. void requestDmMessages(const QString& channelId); + // Не более двух FFmpeg jobs; GUI callback защищён QPointer. void prepareVideoThumbnail(const QString& path); + // Owned-подписка в uasync и local audio; ошибка восстанавливает кнопку. void onRadioToggled(bool on); void updateRadioUi(); static bool isDmChannel(const QString& channelId);