/* Composer канала/PM: текст, emoji, запись голосового и выбор вложений. * Контекст target фиксируется в запросе вложения; подготовку и отправку выполняет ядро. * Переданный submitAttachment request должен принять и освободить обработчик сигнала. */ #pragma once #include #include #include #include class QTextEdit; class QPushButton; class QLabel; class QProgressBar; class QTimer; class EmojiPanel; class AudioRecorder; struct attachment_send_req; class InputBar : public QWidget { Q_OBJECT public: explicit InputBar(EmojiPanel *emojiPanel, AudioRecorder *recorder, QWidget *parent = nullptr); ~InputBar() override; void setRecordingContext(const QString& mediaDirBase, const QString& channelId); /* Один обработчик URL-drop для composer и viewport истории; отправка остаётся по кнопке/Enter. */ void installFileDropTarget(QWidget* target); /* Общий composer для канала и PM. */ void setDmMode(bool enabled); void setMediaEnabled(bool enabled); void updatePreparation(const QString& target, const QByteArray& job, int state); void setReply(const QByteArray& messageId, const QString& author, const QString& preview); void clearReply(); void submissionResult(quint64 request, bool success); signals: void sendMessage(const QString &text, const QByteArray& replyTo, quint64 request); void submitAttachment(const QString& target, struct attachment_send_req* request); void sendFileAttachment(const QString& target, const QString& mediaSrc, const QString& mediaDest, const QString& displayName, const QByteArray& replyTo, quint64 request); void sendImageAttachment(const QString& target, const QString& mediaSrc, const QString& mediaDest, const QString& displayName, const QByteArray& replyTo, quint64 request); void attachmentRejected(const QString& reason); protected: bool eventFilter(QObject *obj, QEvent *event) override; void resizeEvent(QResizeEvent* event) override; private slots: void onSendClicked(); void adjustInputHeight(); void onPttPressed(); void onPttReleased(); void onAttachClicked(); void clearPendingAttachment(); void updateRecordingUI(int ms); void showRecordOverlay(); void hideRecordOverlay(); private: static QString formatFileSize(qint64 bytes); struct PendingReply { quint64 generation; QString target, text, source; int textRevision; quint64 attachmentRevision; }; quint64 beginReplySend(const QByteArray& reply, quint64 generation, const QString& target, const QString& text = {}, const QString& source = {}); bool replySending() const; QWidget* m_replyPreview = nullptr; QLabel* m_replyLabel = nullptr; QByteArray m_replyTo, m_recordReply; quint64 m_replyGeneration = 0, m_recordReplyGeneration = 0, m_submissionSequence = 0; bool m_replyFailed = false; quint64 m_attachmentRevision = 0; QHash m_pendingReplies; QTextEdit *m_input; QPushButton *m_emojiBtn; QPushButton *m_pttBtn; QPushButton *m_attachBtn = nullptr; EmojiPanel *m_emojiPanel; AudioRecorder *m_recorder; QWidget *m_recordOverlay = nullptr; QLabel *m_recordLabel = nullptr; QPushButton *m_cancelRecBtn = nullptr; QProgressBar *m_levelBar = nullptr; QWidget *m_attachPreview = nullptr; QLabel *m_attachPreviewLabel = nullptr; QPushButton *m_attachClearBtn = nullptr; bool m_pttActive = false; bool m_dmMode = false; QString m_mediaDirBase; QString m_channelIdForRecord; QString m_recordTarget; QString m_pendingAttachSrc; QString m_pendingAttachDest; QString m_pendingAttachName; qint64 m_pendingAttachSize = 0; bool m_pendingAttachAsVideo = false; bool m_pendingAttachAsImage = false; QLabel* m_preparationLabel = nullptr; QHash> m_preparations; QSet m_preparationErrors; void showPreparation(); bool canAttachFile() const; bool prepareAttachment(const QString& source, quint64 context); void rejectAttachment(const QString& reason, const QString& source = QString()); QSet m_dropTargets; QPointer m_dropHint; quint64 m_attachmentContext = 0; };