#include "channellist.h" #include "channeldelegate.h" #include "../db/db_manager.h" #include #include #include #include #include #include #include extern "C" { #include "../../lib/debug_config.h" } /* QListView that prevents deselection, handles invite plus-button clicks */ class ChannelListView : public QListView { Q_OBJECT public: using QListView::QListView; signals: void inviteClicked(const QString& channelId); protected: void mousePressEvent(QMouseEvent* e) override { QModelIndex idx = indexAt(e->pos()); if (!idx.isValid()) return; QRect plusRect = ChannelDelegate::inviteButtonRect(visualRect(idx)); if (plusRect.contains(e->pos())) { emit inviteClicked(idx.data(ChannelChannelIdRole).toString()); return; } QListView::mousePressEvent(e); } void mouseMoveEvent(QMouseEvent* e) override { int prevRow = m_hoveredInviteRow; m_hoveredInviteRow = -1; QModelIndex idx = indexAt(e->pos()); if (idx.isValid()) { QRect plusRect = ChannelDelegate::inviteButtonRect(visualRect(idx)); if (plusRect.contains(e->pos())) m_hoveredInviteRow = idx.row(); } if (m_hoveredInviteRow != prevRow) { model()->setData(model()->index(prevRow, 0), 0, ChannelInviteBtnHoveredRole); model()->setData(model()->index(m_hoveredInviteRow, 0), 1, ChannelInviteBtnHoveredRole); } setCursor(m_hoveredInviteRow >= 0 ? Qt::PointingHandCursor : Qt::ArrowCursor); QListView::mouseMoveEvent(e); } QItemSelectionModel::SelectionFlags selectionCommand(const QModelIndex& idx, const QEvent* e) const override { if (idx.isValid()) return QItemSelectionModel::ClearAndSelect; return QItemSelectionModel::NoUpdate; } void selectionChanged(const QItemSelection& sel, const QItemSelection& desel) override { QListView::selectionChanged(sel, desel); if (selectionModel() && !selectionModel()->hasSelection() && currentIndex().isValid() && currentIndex().row() < model()->rowCount()) selectionModel()->select(currentIndex(), QItemSelectionModel::ClearAndSelect); } private: int m_hoveredInviteRow = -1; }; static QIcon makeChannelIcon(const QColor &color, const QString &letter) { QPixmap pix(32, 32); pix.fill(Qt::transparent); QPainter p(&pix); p.setRenderHint(QPainter::Antialiasing); p.setBrush(color); p.setPen(Qt::NoPen); p.drawRoundedRect(0, 0, 32, 32, 8, 8); p.setPen(Qt::white); QFont f = p.font(); f.setBold(true); f.setPointSize(14); p.setFont(f); p.drawText(QRect(0, 0, 32, 32), Qt::AlignCenter, letter); p.end(); return QIcon(pix); } ChannelList::ChannelList(DbManager* db, QWidget *parent) : QWidget(parent) , m_listView(new ChannelListView(this)) , m_model(new QStandardItemModel(this)) , m_db(db) { auto *layout = new QVBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); auto *header = new QLabel("Channels", this); header->setAlignment(Qt::AlignCenter); header->setStyleSheet("font-weight: bold; padding: 8px; background: palette(window);"); layout->addWidget(header); m_listView->setModel(m_model); m_listView->setItemDelegate(new ChannelDelegate(this)); m_listView->setEditTriggers(QAbstractItemView::NoEditTriggers); m_listView->setSelectionMode(QAbstractItemView::SingleSelection); m_listView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); m_listView->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); m_listView->setMouseTracking(true); layout->addWidget(m_listView); auto *footer = new QWidget(this); auto *fl = new QHBoxLayout(footer); fl->setContentsMargins(6, 4, 6, 4); fl->setSpacing(6); auto *gearBtn = new QPushButton(QString::fromUtf8("\xE2\x9A\x99"), footer); gearBtn->setFixedSize(28, 28); gearBtn->setToolTip(QString::fromUtf8("Настройки")); gearBtn->setStyleSheet( "QPushButton { border: none; font-size: 18px; background: transparent; color: gray; }" "QPushButton:hover { color: palette(text); }"); fl->addWidget(gearBtn); connect(gearBtn, &QPushButton::clicked, this, [this] { emit settingsRequested(); }); fl->addStretch(); auto *createBtn = new QPushButton("GRP", footer); createBtn->setFixedSize(36, 28); createBtn->setToolTip(QString::fromUtf8("Создать группу")); createBtn->setStyleSheet( "QPushButton { border: 1.5px solid #27AE60; border-radius: 6px;" " font-size: 11px; font-weight: bold; color: #27AE60; background: transparent; }" "QPushButton:hover { background: #27AE60; color: white; }"); connect(createBtn, &QPushButton::clicked, this, [this] { emit createGroupRequested(); }); fl->addWidget(createBtn); auto *plusBtn = new QPushButton("+", footer); plusBtn->setFixedSize(28, 28); plusBtn->setToolTip(QString::fromUtf8("Присоединиться")); plusBtn->setStyleSheet( "QPushButton { border: 1.5px solid #3390EC; border-radius: 14px;" " font-size: 18px; font-weight: bold; color: #3390EC; background: transparent; }" "QPushButton:hover { background: #3390EC; color: white; }"); connect(plusBtn, &QPushButton::clicked, this, [this] { emit joinRequested(); }); fl->addWidget(plusBtn); layout->addWidget(footer); connect(m_listView->selectionModel(), &QItemSelectionModel::currentChanged, this, [this](const QModelIndex ¤t, const QModelIndex &) { if (current.isValid()) { QString cid = current.data(ChannelChannelIdRole).toString(); emit channelSelected(cid); } }); connect(static_cast(m_listView), &ChannelListView::inviteClicked, this, &ChannelList::inviteRequested); } void ChannelList::loadChannels() { QString selectedCid; if (m_listView->currentIndex().isValid()) selectedCid = m_listView->currentIndex().data(ChannelChannelIdRole).toString(); auto* sel = m_listView->selectionModel(); sel->blockSignals(true); m_model->clear(); if (m_db && m_db->isOpen()) { int sortMode = m_db->getUiStateInt("channel_sort", 0); auto channels = m_db->getChannels(sortMode == 1 ? ChannelSortMode::Recent : ChannelSortMode::Alphabetical); static const QColor colors[] = { QColor(74,144,226), QColor(126,211,33), QColor(245,166,35), QColor(208,2,27), QColor(144,19,254), QColor(0,184,148) }; int ci = 0; for (const auto& ch : channels) { auto letter = ch.name.mid(0, 1).toUpper(); if (letter.isEmpty()) letter = "#"; auto *item = new QStandardItem(); item->setData(ch.name, ChannelNameRole); item->setData(ch.channelId, ChannelChannelIdRole); item->setData(m_db->getChannelLastMessage(ch.channelId), ChannelLastMessageRole); item->setData(ch.unreadCount, ChannelUnreadCountRole); item->setData(ch.lastMsgAt, ChannelLastMsgAtRole); item->setData(makeChannelIcon(colors[ci % 6], letter), ChannelIconRole); m_model->appendRow(item); DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "ChannelList::loadChannels row=%d cid='%s' name='%s' unread=%d lastMsgAt=%lld sort=%d", m_model->rowCount()-1, qPrintable(ch.channelId), qPrintable(ch.name), ch.unreadCount, (long long)ch.lastMsgAt, sortMode); ci++; } } sel->blockSignals(false); if (!selectedCid.isEmpty()) { selectChannel(selectedCid); } else if (m_model->rowCount() > 0) { QModelIndex idx = m_model->index(0, 0); sel->select(idx, QItemSelectionModel::ClearAndSelect); sel->setCurrentIndex(idx, QItemSelectionModel::NoUpdate); } } void ChannelList::selectChannel(const QString& channelId) { for (int row = 0; row < m_model->rowCount(); row++) { if (m_model->item(row)->data(ChannelChannelIdRole).toString() == channelId) { QModelIndex idx = m_model->index(row, 0); m_listView->selectionModel()->select(idx, QItemSelectionModel::ClearAndSelect); m_listView->selectionModel()->setCurrentIndex(idx, QItemSelectionModel::NoUpdate); return; } } } void ChannelList::setChannelPeersOnline(const QString& channelId, int count) { int found = 0; DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "ChannelList::setChannelPeersOnline search='%s' online=%d rows=%d", qPrintable(channelId), count, m_model->rowCount()); for (int row = 0; row < m_model->rowCount(); row++) { QStandardItem* item = m_model->item(row); if (item) { QString itemCid = item->data(ChannelChannelIdRole).toString(); int match = (itemCid == channelId) ? 1 : 0; DEBUG_INFO(DEBUG_CATEGORY_DEBUG, " row=%d cid='%s' match=%d", row, qPrintable(itemCid), match); if (match) { item->setData(count, ChannelPeersOnlineRole); item->setData(m_autoConnectActive ? 1 : 0, ChannelAutoConnectActiveRole); DEBUG_INFO(DEBUG_CATEGORY_DEBUG, " SET online=%d", count); found = 1; break; } } } if (!found) DEBUG_INFO(DEBUG_CATEGORY_DEBUG, " NOT FOUND in model"); } void ChannelList::setAutoConnectActive(bool active) { m_autoConnectActive = active; for (int row = 0; row < m_model->rowCount(); row++) { QStandardItem* item = m_model->item(row); if (item) item->setData(active ? 1 : 0, ChannelAutoConnectActiveRole); } } void ChannelList::setChannelUnreadCount(const QString& channelId, int count) { for (int row = 0; row < m_model->rowCount(); row++) { QStandardItem* item = m_model->item(row); if (item && item->data(ChannelChannelIdRole).toString() == channelId) { int old = item->data(ChannelUnreadCountRole).toInt(); DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "ChannelList::setChannelUnreadCount ch=%s %d->%d", qPrintable(channelId), old, count); item->setData(count, ChannelUnreadCountRole); emit m_model->dataChanged(m_model->index(row, 0), m_model->index(row, 0)); return; } } DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "ChannelList::setChannelUnreadCount ch=%s not found", qPrintable(channelId)); } int ChannelList::getChannelUnreadCount(const QString& channelId) const { for (int row = 0; row < m_model->rowCount(); row++) { QStandardItem* item = m_model->item(row); if (item && item->data(ChannelChannelIdRole).toString() == channelId) return item->data(ChannelUnreadCountRole).toInt(); } return 0; } #include "channellist.moc"