Files
utun2/tools/chatgui/src/chatview.h
T

85 lines
8.8 KiB
C++

/* GUI-вид плоской истории: фон, собственное выделение/clipboard, ссылки и прокрутка; данные и delegate задаёт владелец.
* Модель предоставляет Msg*Role из messagedelegate.h. MessageDelegate рисует текст и вычисляет UTF-16 границы выделения.
* Для ссылок установить hit-test, для клавиш composer/окна — установить этот объект их eventFilter.
* Сигналы анимации/последнего окна только уведомляют владельца: сам вид не запускает таймеры emoji и не загружает историю. */
#pragma once
#include <QListView>
#include <QPersistentModelIndex>
#include <QPoint>
#include <QPushButton>
#include <QSet>
#include <functional>
struct ChatTextSelection {
QPersistentModelIndex index; // Строка начала drag, для которой delegate вычисляет диапазон текста.
QPoint clickPos; // Точка press относительно visualRect(index).topLeft().
QPoint dragPos; // Текущая/последняя точка drag в той же системе координат.
int startChar = -1; // UTF-16 начало [startChar,endChar); -1 — delegate ещё не вычислил границы.
int endChar = -1; // UTF-16 конец без включения; недопустимый/пустой диапазон при Copy даёт весь текст строки.
bool active = false; // Есть drag-выделение, в том числе после release; не равно isSelecting().
};
class ChatView : public QListView {
Q_OBJECT
public:
/* parent владеет виджетом; создаёт свой вертикальный scrollbar и кнопку вниз, но не модель/MessageDelegate. */
explicit ChatView(QWidget *parent = nullptr);
/* Загрузить QPixmap из файла/ресурса, затемнить чёрным с alpha=64/255 и запросить repaint мозаичного фона.
* Ошибка загрузки даёт WARN и возврат без запроса repaint. */
void setBackgroundImage(const QString &path);
void setHistoricalWindow(bool enabled); // Старое окно истории: кнопка вниз видима и на нижней позиции; данные не меняются.
bool hasSelection() const { return m_selStart.isValid(); } // Проверяет собственное выделение, не QItemSelectionModel.
QModelIndex selStart() const { return QModelIndex(m_selStart); } // Строка начала drag; может быть ниже selEnd.
QModelIndex selEnd() const { return QModelIndex(m_selEnd); } // Последняя строка drag; индексы могут стать invalid у модели.
bool isSelecting() const { return m_selecting; } // Кнопка ещё удерживается и drag уже преодолел порог 5 px.
const ChatTextSelection &textSelection() const { return m_textSel; } // Заимствованный до следующего изменения состояния вида.
bool isRowSelected(const QModelIndex &idx) const; // Включительный диапазон номеров строк; idx ожидается из той же модели.
void setTextSelChars(int start, int end) { m_textSel.startChar = start; m_textSel.endChar = end; } // Обратная связь от delegate.
/* Сохранить hit-test ссылки: fn(index, viewport point) → строка ссылки либо пусто. Пустой fn отключает ссылки.
* Callback вызывается в GUI при hover/release; hover может передать invalid index.
* Вызывающий обеспечивает lifetime захваченных объектов на время их использования callback. */
void setLinkHitTest(std::function<QString(const QModelIndex&, const QPoint&)> fn) { m_linkHitTest = std::move(fn); }
signals:
void animationTriggered(); // Новое видимое сочетание сообщение/путь анимации или вход курсора в строку с анимацией.
void linkActivated(const QString& link); // Левый click без drag: press/release на одном index, ссылка получена hit-test.
void latestRequested(); // End без модификаторов либо кнопка вниз, до локального scrollToBottom/перемещения scrollbar.
protected:
/* Up/Down/PgUp/PgDown/End без модификаторов от внешнего объекта; обрабатывает только visible/enabled вид. */
bool eventFilter(QObject *object, QEvent *event) override;
bool viewportEvent(QEvent *event) override; // Leave снимает hover-index, чтобы новый вход снова запустил анимацию.
void paintEvent(QPaintEvent *event) override; // Фон/обычное рисование, затем снимок видимых анимаций по ролям текста/реакций.
void resizeEvent(QResizeEvent *event) override; // Планирует layout строк и обновляет кнопку вниз.
void scrollContentsBy(int dx, int dy) override; // Базовая прокрутка и repaint viewport для tiled-фона/анимаций.
void mousePressEvent(QMouseEvent *event) override; // Левый press сбрасывает прежнее выделение и запоминает строку/точку.
void mouseMoveEvent(QMouseEvent *event) override; // Hover/cursor ссылки либо drag: диапазон строк и точки текстового выделения.
void mouseReleaseEvent(QMouseEvent *event) override; // Ссылка при click, иначе завершение drag с сохранением выделения.
void keyPressEvent(QKeyEvent *event) override; // Клавиши прокрутки, Copy собственного выделения, затем базовый QListView.
private:
/* Прокрутить историю по клавише без модификаторов; true, если событие принято. Фокус сохраняется. */
bool scrollWithKey(QKeyEvent *event);
/* Одна строка: выбранный UTF-16 фрагмент либо весь MsgTextRole; несколько — [time] author: text по возрастанию row.
* Пишет только QClipboard::Clipboard, без selection clipboard; без собственного выделения ничего не меняет. */
void copySelection();
void updateScrollDownBtn(); // Видимость по historical/atBottom; выше низа позиционирует кнопку справа внизу viewport.
QPixmap m_background; // Собственные пиксели уже с затемнением, рисуются мозаикой без масштабирования.
QPersistentModelIndex m_selStart; // Начало собственного выделения, Qt отслеживает изменения индекса модели.
QPersistentModelIndex m_selEnd; // Последняя строка диапазона, направление drag сохраняется.
ChatTextSelection m_textSel; // Точки и кеш границ текста начальной строки; active сохраняется после release.
QPersistentModelIndex m_lastHovered; // Для однократного animationTriggered при входе в другую строку.
QSet<QString> m_visibleAnimations; // Ключ channel/id/outgoing/path на последнем paint; удаление из видимости само не даёт сигнал.
QPersistentModelIndex m_pressIndex; // Кандидат click/начала drag, снимается при release.
QPoint m_pressPos; // Координата события press для порога drag и проверки click.
bool m_selecting = false; // Идёт drag собственного выделения, не состояние стандартной модели выбора.
bool m_historicalWindow = false; // Выбран исторический снимок, владелец должен обработать latestRequested.
std::function<QString(const QModelIndex&, const QPoint&)> m_linkHitTest; // Собственная копия callback и его захваченных значений.
QPushButton *m_scrollDownBtn = nullptr; // QObject-child вида: latestRequested и затем локальная прокрутка вниз.
};