#include "MessagePanel.h" #include "JS8_Include/EventFilter.h" #include "JS8_Main/Radio.h" #include "JS8_Widgets/DateTableWidgetItem.h" #include "JS8_Widgets/SemiSortableHeader.h" #include "ui_MessagePanel.h" #include #include #include #include #include "moc_MessagePanel.cpp" namespace { auto pathSegs(QString const &path) { auto segs = path.split('>'); std::reverse(segs.begin(), segs.end()); return segs; } } // namespace MessagePanel::MessagePanel(QString inboxPath, QWidget *parent) : QWidget(parent), ui(new Ui::MessagePanel), inbox(new Inbox(inboxPath)) { this->call = "%"; inbox->open(); ui->setupUi(this); // connect selection model changed connect(ui->messageTableWidget->selectionModel(), &QItemSelectionModel::selectionChanged, this, &MessagePanel::messageTableSelectionChanged); // reply when key pressed in the reply box ui->replytextEdit->installEventFilter(new EventFilter::EnterKeyPress( [this](QKeyEvent *const event) { if (event->modifiers() & Qt::ShiftModifier) return false; ui->replyPushButton->click(); return true; }, this)); ui->messageTableWidget->setContextMenuPolicy(Qt::ActionsContextMenu); auto deleteAction = new QAction("Delete", ui->messageTableWidget); deleteAction->setShortcut(QKeySequence(Qt::Key_Delete)); deleteAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); connect(deleteAction, &QAction::triggered, this, [this]() { deleteSelectedMessages(); }); ui->messageTableWidget->addAction(deleteAction); auto *table = ui->messageTableWidget; auto *hdr = new SemiSortableHeader(Qt::Horizontal, table); hdr->addNonSortableColumn(6); hdr->addNonSortableColumn(0); hdr->setSectionResizeMode(QHeaderView::Interactive); hdr->setStretchLastSection(true); hdr->attachTo(table); // Now that the custom header is installed; indicator state will stick { QSignalBlocker b(hdr); hdr->setSortIndicator(2, Qt::DescendingOrder); } refresh(); } MessagePanel::~MessagePanel() { delete ui; } void MessagePanel::setCall(const QString &call) { this->call = call; setWindowTitle(QString("Messages: %1").arg(call == "%" ? "All" : call)); if (!inbox) return; refresh(); } void MessagePanel::refresh() { QList> msgs = inbox->fetchForCall(this->call); populateMessages(msgs); emit countsUpdated(); } void MessagePanel::populateMessages(QList> msgs) { SemiSortableHeader *hdr = static_cast( ui->messageTableWidget->horizontalHeader()); // Remember sort state (avoid sorting by unread flag column 0) const int rememberedCol = hdr->sortIndicatorSection() >= 0 ? hdr->sortIndicatorSection() : 2; const int sortCol = (rememberedCol == 0 ? 2 : rememberedCol); const Qt::SortOrder sortOrder = hdr->sortIndicatorOrder(); // Freeze behavior while populating ui->messageTableWidget->setUpdatesEnabled(false); const bool wasSorting = ui->messageTableWidget->isSortingEnabled(); ui->messageTableWidget->setSortingEnabled(false); for (int i = ui->messageTableWidget->rowCount(); i >= 0; i--) { ui->messageTableWidget->removeRow(i); } ui->messageTableWidget->setUpdatesEnabled(false); { foreach (auto pair, msgs) { auto mid = pair.first; auto msg = pair.second; auto params = msg.params(); int row = ui->messageTableWidget->rowCount(); ui->messageTableWidget->insertRow(row); int col = 0; auto typeItem = new QTableWidgetItem(msg.type() == "UNREAD" ? "\u2691" : ""); typeItem->setData(Qt::UserRole, msg.type()); typeItem->setTextAlignment(Qt::AlignCenter); ui->messageTableWidget->setItem(row, col++, typeItem); auto midItem = new QTableWidgetItem(); midItem->setData(Qt::EditRole, mid); midItem->setData(Qt::DisplayRole, QString::number(mid)); midItem->setTextAlignment(Qt::AlignCenter); ui->messageTableWidget->setItem(row, col++, midItem); const auto dateStr = params.value("UTC").toString(); QDateTime ts = QDateTime::fromString(dateStr, "yyyy-MM-dd HH:mm:ss"); ts.setTimeZone(QTimeZone::utc()); auto *dateItem = new DateItem(ts.toString("ddd MMM d HH:mm:ss yyyy")); dateItem->setData(Qt::UserRole, ts.toSecsSinceEpoch()); // sort key dateItem->setTextAlignment(Qt::AlignCenter); ui->messageTableWidget->setItem(row, col++, dateItem); auto dial = (quint64)params.value("DIAL").toInt(); auto dialItem = new QTableWidgetItem(); dialItem->setData(Qt::EditRole, dial); dialItem->setData(Qt::DisplayRole, QString("%1 MHz").arg(Radio::pretty_frequency_MHz_string(dial))); dialItem->setTextAlignment(Qt::AlignCenter); ui->messageTableWidget->setItem(row, col++, dialItem); auto path = params.value("PATH").toString(); auto segs = pathSegs(path); auto fromItem = new QTableWidgetItem(segs.join(" via ")); fromItem->setData(Qt::UserRole, path); fromItem->setTextAlignment(Qt::AlignCenter); ui->messageTableWidget->setItem(row, col++, fromItem); auto to = params.value("TO").toString(); auto toItem = new QTableWidgetItem(to); toItem->setData(Qt::UserRole, to); toItem->setTextAlignment(Qt::AlignCenter); ui->messageTableWidget->setItem(row, col++, toItem); auto text = params.value("TEXT").toString(); auto textItem = new QTableWidgetItem(text); textItem->setData(Qt::UserRole, text); textItem->setTextAlignment(Qt::AlignVCenter); ui->messageTableWidget->setItem(row, col++, textItem); } ui->messageTableWidget->resizeColumnToContents(0); ui->messageTableWidget->resizeColumnToContents(1); ui->messageTableWidget->resizeColumnToContents(2); ui->messageTableWidget->resizeColumnToContents(3); ui->messageTableWidget->resizeColumnToContents(4); ui->messageTableWidget->resizeColumnToContents(5); } ui->messageTableWidget->setUpdatesEnabled(true); // Unfreeze + restore sort once, at the end ui->messageTableWidget->setSortingEnabled(wasSorting); // or true if you want arrows always ui->messageTableWidget->setUpdatesEnabled(true); if (hdr) { QSignalBlocker b(hdr); hdr->setSortIndicator(sortCol, sortOrder); } if (wasSorting) { ui->messageTableWidget->sortItems(sortCol, sortOrder); } ui->messageTableWidget->viewport()->update(); emit countsUpdated(); } void MessagePanel::deleteSelectedMessages() { auto items = ui->messageTableWidget->selectedItems(); if (items.isEmpty()) { return; } // Collect unique rows and their message IDs QMap rowsToDelete; // row -> message id for (auto item : items) { int row = item->row(); if (rowsToDelete.contains(row)) { continue; } auto col = ui->messageTableWidget->item(row, 1); if (!col) { continue; } bool ok = false; auto mid = col->data(Qt::EditRole).toInt(&ok); if (!ok) { continue; } rowsToDelete.insert(row, mid); } // Delete rows in reverse order to avoid index shifting auto rows = rowsToDelete.keys(); std::sort(rows.begin(), rows.end(), std::greater()); for (int row : rows) { ui->messageTableWidget->removeRow(row); deleteMessage(rowsToDelete[row]); } emit countsUpdated(); } void MessagePanel::deleteMessage(int id) { if (!inbox->open()) { return; } inbox->del(id); } void MessagePanel::markMessageRead(int id) { if (!inbox->open()) { return; } auto msg = inbox->value(id); if (msg.type() == "UNREAD") { msg.setType("READ"); inbox->set(id, msg); // Also clear the unread flag in the table UI for this message id auto *table = ui->messageTableWidget; if (table) { for (int row = 0; row < table->rowCount(); ++row) { auto *idItem = table->item(row, 1); // column 1 holds the message id if (!idItem) continue; bool okId = false; int midInRow = idItem->data(Qt::EditRole).toInt(&okId); if (!okId) continue; if (midInRow == id) { auto *flagItem = table->item(row, 0); // column 0 is the unread flag if (flagItem) { flagItem->setText(""); flagItem->setData(Qt::UserRole, ""); } break; } } } emit countsUpdated(); } } QString MessagePanel::prepareReplyMessage(QString path, QString text) { return QString("%1 MSG %2").arg(path).arg(text); } void MessagePanel::messageTableSelectionChanged( const QItemSelection &selected, const QItemSelection &/*deselected*/) { auto items = ui->messageTableWidget->selectedItems(); if (items.isEmpty() || items.size() > ui->messageTableWidget->columnCount()) { ui->messageTextEdit->clear(); return; } auto firstItem = items.first(); auto row = firstItem->row(); // message column auto item = ui->messageTableWidget->item( row, ui->messageTableWidget->columnCount() - 1); if (!item) { return; } auto text = item->data(Qt::UserRole).toString(); ui->messageTextEdit->setPlainText(text); // Mark deselected as read in table auto selectedItemIndexes = selected.indexes(); if (selectedItemIndexes.empty()) { return; } auto selectedRowIndex = selectedItemIndexes.first(); auto selectedRow = selectedRowIndex.row(); auto readFlagItem = ui->messageTableWidget->item(selectedRow, 0); readFlagItem->setText(""); readFlagItem->setData(Qt::UserRole, ""); // Mark message read in DB auto col = ui->messageTableWidget->item(selectedRow, 1); if (!col) { return; } bool ok = false; auto mid = col->data(Qt::EditRole).toInt(&ok); if (!ok) { return; } markMessageRead(mid); } void MessagePanel::on_replyPushButton_clicked() { auto row = ui->messageTableWidget->currentRow(); // from column auto item = ui->messageTableWidget->item( row, ui->messageTableWidget->columnCount() - 3); if (!item) { return; } auto path = item->data(Qt::UserRole).toString(); auto text = "[MESSAGE]"; auto message = prepareReplyMessage(path, text); emit replyMessage(message); }