From dfe037423ffe9a8079ffd0e1602b578cad8a2c98 Mon Sep 17 00:00:00 2001 From: Bill Somerville Date: Wed, 28 Mar 2018 22:25:46 +0000 Subject: [PATCH] New incoming UDP message to allow external applications to highlight decoded callsigns UDP servers can request that WSJT-X clients highlight a specified callsign in the Band Activity decodes window. Either the last occurrence of the callsign may be highlighted or all past and future occurrences can be highlighted. The latter case WSJT-X will remember the callsign and requested highlighting options so that future occurrences can be correctly highlighted. Either or both of the text background color and the text foreground color may be specified. A further UDP message may be sent to change the persistent color highlighting for a given callsign, including reseting persistent highlighting by passing an invalid color value. Thanks to Alex, VE3NEA, for this contribution. git-svn-id: svn+ssh://svn.code.sf.net/p/wsjt/wsjt/branches/wsjtx@8589 ab8295b8-cf94-4d9e-aec4-7959e3be5d79 --- CMakeLists.txt | 2 +- MessageClient.cpp | 15 ++ MessageClient.hpp | 5 + MessageServer.cpp | 13 ++ MessageServer.hpp | 7 + NetworkMessage.hpp | 24 ++- UDPExamples/ClientWidget.cpp | 19 ++- UDPExamples/ClientWidget.hpp | 10 +- UDPExamples/MessageAggregatorMainWindow.cpp | 112 +++++++++++++- UDPExamples/MessageAggregatorMainWindow.hpp | 18 ++- displaytext.cpp | 154 +++++++++++++++++++- displaytext.h | 8 +- mainwindow.cpp | 2 + 13 files changed, 374 insertions(+), 15 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e81dc64c5..ad050458e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1362,7 +1362,7 @@ set_target_properties (wsjtx_udp-static PROPERTIES ) target_compile_definitions (wsjtx_udp-static PUBLIC UDP_STATIC_DEFINE) #qt5_use_modules (wsjtx_udp Network) -qt5_use_modules (wsjtx_udp-static Network) +qt5_use_modules (wsjtx_udp-static Network Gui) generate_export_header (wsjtx_udp-static BASE_NAME udp) add_executable (udp_daemon UDPExamples/UDPDaemon.cpp UDPExamples/udp_daemon.rc ${WSJTX_ICON_FILE}) diff --git a/MessageClient.cpp b/MessageClient.cpp index 2b6d9e308..68d6f0fdc 100644 --- a/MessageClient.cpp +++ b/MessageClient.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include "NetworkMessage.hpp" @@ -218,6 +219,20 @@ void MessageClient::impl::parse_message (QByteArray const& msg) } break; + case NetworkMessage::HighlightCallsign: + { + QByteArray call; + QColor bg; // default invalid color + QColor fg; // default invalid color + bool last_only {false}; + in >> call >> bg >> fg >> last_only; + if (check_status (in) != Fail && call.size ()) + { + Q_EMIT self_->highlight_callsign (QString::fromUtf8 (call), bg, fg, last_only); + } + } + break; + default: // Ignore // diff --git a/MessageClient.hpp b/MessageClient.hpp index f0719ac55..be524fdb8 100644 --- a/MessageClient.hpp +++ b/MessageClient.hpp @@ -11,6 +11,7 @@ class QByteArray; class QHostAddress; +class QColor; // // MessageClient - Manage messages sent and replies received from a @@ -95,6 +96,10 @@ public: // message text Q_SIGNAL void free_text (QString const&, bool send); + // this signal is emitted if the server has sent a highlight + // callsign request for the specified call + Q_SIGNAL void highlight_callsign (QString const& callsign, QColor const& bg, QColor const& fg, bool last_only); + // this signal is emitted when network errors occur or if a host // lookup fails Q_SIGNAL void error (QString const&) const; diff --git a/MessageServer.cpp b/MessageServer.cpp index 724da4afc..b5a719e74 100644 --- a/MessageServer.cpp +++ b/MessageServer.cpp @@ -480,3 +480,16 @@ void MessageServer::location (QString const& id, QString const& loc) m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_); } } + +void MessageServer::highlight_callsign (QString const& id, QString const& callsign + , QColor const& bg, QColor const& fg, bool last_only) +{ + auto iter = m_->clients_.find (id); + if (iter != std::end (m_->clients_)) + { + QByteArray message; + NetworkMessage::Builder out {&message, NetworkMessage::HighlightCallsign, id, (*iter).negotiated_schema_number_}; + out << callsign.toUtf8 () << bg << fg << last_only; + m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_); + } +} diff --git a/MessageServer.hpp b/MessageServer.hpp index 77c976fa4..bb7501529 100644 --- a/MessageServer.hpp +++ b/MessageServer.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include "udp_export.h" #include "Radio.hpp" @@ -62,6 +63,12 @@ public: // ask the client with identification 'id' to set the location provided Q_SLOT void location (QString const& id, QString const& location); + // ask the client with identification 'id' to highlight the callsign + // specified with the given colors + Q_SLOT void highlight_callsign (QString const& id, QString const& callsign + , QColor const& bg = QColor {}, QColor const& fg = QColor {} + , bool last_only = false); + // the following signals are emitted when a client broadcasts the // matching message Q_SIGNAL void client_opened (QString const& id, QString const& version, QString const& revision); diff --git a/NetworkMessage.hpp b/NetworkMessage.hpp index 479575a32..acf64fb32 100644 --- a/NetworkMessage.hpp +++ b/NetworkMessage.hpp @@ -350,7 +350,7 @@ * * Logged ADIF Out 12 quint32 * Id (unique key) utf8 - * ADIF text ASCII (serialized like utf8) + * ADIF text utf8 * * The logged ADIF message is sent to the server(s) when the * WSJT-X user accepts the "Log QSO" dialog by clicking the "OK" @@ -368,6 +368,27 @@ * Note that receiving applications can treat the whole message * as a valid ADIF file with one record without special parsing. * + * + * Highlight Callsign In 13 quint32 + * Id (unique key) utf8 + * Callsign utf8 + * Background Color QColor + * Foreground Color QColor + * Highlight last bool + * + * The server may send this message at any time. The message + * specifies the background and foreground color that will be + * used to highlight the specified callsign in the decoded + * messages printed in the Band Activity panel. The WSJT-X + * clients maintain a list of such instructions and apply them to + * all decoded messages in the band activity window. To clear + * highlighting send an invalid QColor value for either or both + * of the background and foreground fields. + * + * The "Highlight last" field allows the sender to request that + * the last instance only instead of all instances of the + * specified call be highlighted or have it's highlighting + * cleared. */ #include @@ -396,6 +417,7 @@ namespace NetworkMessage WSPRDecode, Location, LoggedADIF, + HighlightCallsign, maximum_message_type_ // ONLY add new message types // immediately before here }; diff --git a/UDPExamples/ClientWidget.cpp b/UDPExamples/ClientWidget.cpp index 001041cae..06b09ba4d 100644 --- a/UDPExamples/ClientWidget.cpp +++ b/UDPExamples/ClientWidget.cpp @@ -115,9 +115,10 @@ namespace ClientWidget::ClientWidget (QAbstractItemModel * decodes_model, QAbstractItemModel * beacons_model , QString const& id, QString const& version, QString const& revision - , QWidget * parent) + , QListWidget const * calls_of_interest, QWidget * parent) : QDockWidget {make_title (id, version, revision), parent} , id_ {id} + , calls_of_interest_ {calls_of_interest} , decodes_proxy_model_ {id_} , decodes_table_view_ {new QTableView} , beacons_table_view_ {new QTableView} @@ -216,11 +217,27 @@ ClientWidget::ClientWidget (QAbstractItemModel * decodes_model, QAbstractItemMod // setMinimumSize (QSize {550, 0}); setFeatures (DockWidgetMovable | DockWidgetFloatable); setAllowedAreas (Qt::BottomDockWidgetArea); + setFloating (true); // connect up table view signals connect (decodes_table_view_, &QTableView::doubleClicked, this, [this] (QModelIndex const& index) { Q_EMIT do_reply (decodes_proxy_model_.mapToSource (index), QApplication::keyboardModifiers () >> 24); }); + + // tell new client about calls of interest + for (int row = 0; row < calls_of_interest_->count (); ++row) + { + Q_EMIT highlight_callsign (id_, calls_of_interest_->item (row)->text (), QColor {Qt::blue}, QColor {Qt::yellow}); + } +} + +ClientWidget::~ClientWidget () +{ + for (int row = 0; row < calls_of_interest_->count (); ++row) + { + // tell client to forget calls of interest + Q_EMIT highlight_callsign (id_, calls_of_interest_->item (row)->text ()); + } } void ClientWidget::update_status (QString const& id, Frequency f, QString const& mode, QString const& dx_call diff --git a/UDPExamples/ClientWidget.hpp b/UDPExamples/ClientWidget.hpp index 0cec23e7a..29059bfa5 100644 --- a/UDPExamples/ClientWidget.hpp +++ b/UDPExamples/ClientWidget.hpp @@ -11,6 +11,7 @@ class QAbstractItemModel; class QModelIndex; +class QColor; using Frequency = MessageServer::Frequency; @@ -22,7 +23,8 @@ class ClientWidget public: explicit ClientWidget (QAbstractItemModel * decodes_model, QAbstractItemModel * beacons_model , QString const& id, QString const& version, QString const& revision - , QWidget * parent = nullptr); + , QListWidget const * calls_of_interest, QWidget * parent = nullptr); + ~ClientWidget (); bool fast_mode () const {return fast_mode_;} @@ -43,10 +45,14 @@ public: Q_SIGNAL void do_reply (QModelIndex const&, quint8 modifier); Q_SIGNAL void do_halt_tx (QString const& id, bool auto_only); Q_SIGNAL void do_free_text (QString const& id, QString const& text, bool); - Q_SIGNAL void location (QString const &id, QString const &text); + Q_SIGNAL void location (QString const& id, QString const& text); + Q_SIGNAL void highlight_callsign (QString const& id, QString const& call + , QColor const& bg = QColor {}, QColor const& fg = QColor {} + , bool last_only = false); private: QString id_; + QListWidget const * calls_of_interest_; class IdFilterModel final : public QSortFilterProxyModel { diff --git a/UDPExamples/MessageAggregatorMainWindow.cpp b/UDPExamples/MessageAggregatorMainWindow.cpp index 324c7d0e5..1d1dce5d6 100644 --- a/UDPExamples/MessageAggregatorMainWindow.cpp +++ b/UDPExamples/MessageAggregatorMainWindow.cpp @@ -36,6 +36,11 @@ MessageAggregatorMainWindow::MessageAggregatorMainWindow () , server_ {new MessageServer {this}} , multicast_group_line_edit_ {new QLineEdit} , log_table_view_ {new QTableView} + , add_call_of_interest_action_ {new QAction {tr ("&Add callsign"), this}} + , delete_call_of_interest_action_ {new QAction {tr ("&Delete callsign"), this}} + , last_call_of_interest_action_ {new QAction {tr ("&Highlight last only"), this}} + , call_of_interest_bg_colour_action_ {new QAction {tr ("&Background colour"), this}} + , call_of_interest_fg_colour_action_ {new QAction {tr ("&Foreground colour"), this}} { // logbook int column {0}; @@ -83,6 +88,91 @@ MessageAggregatorMainWindow::MessageAggregatorMainWindow () setDockOptions (AnimatedDocks | AllowNestedDocks | AllowTabbedDocks); setTabPosition (Qt::BottomDockWidgetArea, QTabWidget::North); + QDockWidget * calls_dock {new QDockWidget {tr ("Calls of Interest"), this}}; + calls_dock->setAllowedAreas (Qt::RightDockWidgetArea); + calls_of_interest_ = new QListWidget {calls_dock}; + calls_of_interest_->setContextMenuPolicy (Qt::ActionsContextMenu); + calls_of_interest_->insertAction (nullptr, add_call_of_interest_action_); + connect (add_call_of_interest_action_, &QAction::triggered, [this] () { + auto item = new QListWidgetItem {}; + item->setFlags (item->flags () | Qt::ItemIsEditable); + item->setData (Qt::UserRole, QString {}); + calls_of_interest_->addItem (item); + calls_of_interest_->editItem (item); + }); + calls_of_interest_->insertAction (nullptr, delete_call_of_interest_action_); + connect (delete_call_of_interest_action_, &QAction::triggered, [this] () { + for (auto item : calls_of_interest_->selectedItems ()) + { + auto old_call = item->data (Qt::UserRole); + if (old_call.isValid ()) change_highlighting (old_call.toString ()); + delete item; + } + }); + calls_of_interest_->insertAction (nullptr, last_call_of_interest_action_); + connect (last_call_of_interest_action_, &QAction::triggered, [this] () { + for (auto item : calls_of_interest_->selectedItems ()) + { + auto old_call = item->data (Qt::UserRole); + change_highlighting (old_call.toString ()); + change_highlighting (old_call.toString () + , item->background ().color (), item->foreground ().color (), true); + delete item; + } + }); + calls_of_interest_->insertAction (nullptr, call_of_interest_bg_colour_action_); + connect (call_of_interest_bg_colour_action_, &QAction::triggered, [this] () { + for (auto item : calls_of_interest_->selectedItems ()) + { + auto old_call = item->data (Qt::UserRole); + auto new_colour = QColorDialog::getColor (item->background ().color () + , this, tr ("Select background color")); + if (new_colour.isValid ()) + { + change_highlighting (old_call.toString (), new_colour, item->foreground ().color ()); + item->setBackground (new_colour); + } + } + }); + calls_of_interest_->insertAction (nullptr, call_of_interest_fg_colour_action_); + connect (call_of_interest_fg_colour_action_, &QAction::triggered, [this] () { + for (auto item : calls_of_interest_->selectedItems ()) + { + auto old_call = item->data (Qt::UserRole); + auto new_colour = QColorDialog::getColor (item->foreground ().color () + , this, tr ("Select foreground color")); + if (new_colour.isValid ()) + { + change_highlighting (old_call.toString (), item->background ().color (), new_colour); + item->setForeground (new_colour); + } + } + }); + connect (calls_of_interest_, &QListWidget::itemChanged, [this] (QListWidgetItem * item) { + auto old_call = item->data (Qt::UserRole); + auto new_call = item->text ().toUpper (); + if (new_call != old_call) + { + // tell all clients + if (old_call.isValid ()) + { + change_highlighting (old_call.toString ()); + } + item->setData (Qt::UserRole, new_call); + item->setText (new_call); + auto bg = item->listWidget ()->palette ().text ().color (); + auto fg = item->listWidget ()->palette ().base ().color (); + item->setBackground (bg); + item->setForeground (fg); + change_highlighting (new_call, bg, fg); + } + }); + + calls_dock->setWidget (calls_of_interest_); + addDockWidget (Qt::RightDockWidgetArea, calls_dock); + view_menu_->addAction (calls_dock->toggleViewAction ()); + view_menu_->addSeparator (); + // connect up server connect (server_, &MessageServer::error, [this] (QString const& message) { QMessageBox::warning (this, QApplication::applicationName (), tr ("Network Error"), message); @@ -144,7 +234,7 @@ void MessageAggregatorMainWindow::log_qso (QString const& /*id*/, QDateTime time void MessageAggregatorMainWindow::add_client (QString const& id, QString const& version, QString const& revision) { - auto dock = new ClientWidget {decodes_model_, beacons_model_, id, version, revision, this}; + auto dock = new ClientWidget {decodes_model_, beacons_model_, id, version, revision, calls_of_interest_, this}; dock->setAttribute (Qt::WA_DeleteOnClose); auto view_action = dock->toggleViewAction (); view_action->setEnabled (true); @@ -159,8 +249,9 @@ void MessageAggregatorMainWindow::add_client (QString const& id, QString const& connect (dock, &ClientWidget::do_free_text, server_, &MessageServer::free_text); connect (dock, &ClientWidget::location, server_, &MessageServer::location); connect (view_action, &QAction::toggled, dock, &ClientWidget::setVisible); + connect (dock, &ClientWidget::highlight_callsign, server_, &MessageServer::highlight_callsign); dock_widgets_[id] = dock; - server_->replay (id); + server_->replay (id); // request decodes and status } void MessageAggregatorMainWindow::remove_client (QString const& id) @@ -173,4 +264,21 @@ void MessageAggregatorMainWindow::remove_client (QString const& id) } } +MessageAggregatorMainWindow::~MessageAggregatorMainWindow () +{ + for (auto client : dock_widgets_) + { + delete client; + } +} + +void MessageAggregatorMainWindow::change_highlighting (QString const& call, QColor const& bg, QColor const& fg + , bool last_only) +{ + for (auto id : dock_widgets_.keys ()) + { + server_->highlight_callsign (id, call, bg, fg, last_only); + } +} + #include "moc_MessageAggregatorMainWindow.cpp" diff --git a/UDPExamples/MessageAggregatorMainWindow.hpp b/UDPExamples/MessageAggregatorMainWindow.hpp index 4ee5bface..b699bee93 100644 --- a/UDPExamples/MessageAggregatorMainWindow.hpp +++ b/UDPExamples/MessageAggregatorMainWindow.hpp @@ -15,6 +15,7 @@ class BeaconsModel; class QLineEdit; class QTableView; class ClientWidget; +class QListWidget; using Frequency = MessageServer::Frequency; @@ -25,6 +26,7 @@ class MessageAggregatorMainWindow public: MessageAggregatorMainWindow (); + ~MessageAggregatorMainWindow (); Q_SLOT void log_qso (QString const& /*id*/, QDateTime time_off, QString const& dx_call, QString const& dx_grid , Frequency dial_frequency, QString const& mode, QString const& report_sent @@ -35,6 +37,12 @@ public: private: void add_client (QString const& id, QString const& version, QString const& revision); void remove_client (QString const& id); + void change_highlighting (QString const& call, QColor const& bg = QColor {}, QColor const& fg = QColor {}, + bool last_only = false); + + // maps client id to widgets + using ClientsDictionary = QHash; + ClientsDictionary dock_widgets_; QStandardItemModel * log_; QMenu * view_menu_; @@ -43,10 +51,12 @@ private: MessageServer * server_; QLineEdit * multicast_group_line_edit_; QTableView * log_table_view_; - - // maps client id to widgets - using ClientsDictionary = QHash; - ClientsDictionary dock_widgets_; + QListWidget * calls_of_interest_; + QAction * add_call_of_interest_action_; + QAction * delete_call_of_interest_action_; + QAction * last_call_of_interest_action_; + QAction * call_of_interest_bg_colour_action_; + QAction * call_of_interest_fg_colour_action_; }; #endif diff --git a/displaytext.cpp b/displaytext.cpp index 0fa3682d7..a52d02b91 100644 --- a/displaytext.cpp +++ b/displaytext.cpp @@ -72,7 +72,7 @@ void DisplayText::insertLineSpacer(QString const& line) appendText (line, "#d3d3d3"); } -void DisplayText::appendText(QString const& text, QColor bg) +void DisplayText::appendText(QString const& text, QColor bg, QString const& call1, QString const& call2) { auto cursor = textCursor (); cursor.movePosition (QTextCursor::End); @@ -89,7 +89,59 @@ void DisplayText::appendText(QString const& text, QColor bg) { cursor.insertBlock (block_format); } - cursor.insertText (text); + + QTextCharFormat format = cursor.charFormat(); + format.clearBackground(); + int text_index {0}; + if (call1.size ()) + { + auto call_index = text.indexOf (call1); + if (call_index != -1) // sanity check + { + auto pos = highlighted_calls_.find (call1); + if (pos != highlighted_calls_.end ()) + { + cursor.insertText(text.left (call_index), format); + if (pos.value ().first.isValid ()) + { + format.setBackground (pos.value ().first); + } + if (pos.value ().second.isValid ()) + { + format.setForeground (pos.value ().second); + } + cursor.insertText(text.mid (call_index, call1.size ()), format); + text_index = call_index + call1.size (); + } + } + } + if (call2.size ()) + { + auto call_index = text.indexOf (call2, text_index); + if (call_index != -1) // sanity check + { + auto pos = highlighted_calls_.find (call2); + if (pos != highlighted_calls_.end ()) + { + format.setBackground (bg); + format.clearForeground (); + cursor.insertText(text.mid (text_index, call_index - text_index), format); + if (pos.value ().second.isValid ()) + { + format.setBackground (pos.value ().first); + } + if (pos.value ().second.isValid ()) + { + format.setForeground (pos.value ().second); + } + cursor.insertText(text.mid (call_index, call2.size ()), format); + text_index = call_index + call2.size (); + } + } + } + format.setBackground (bg); + format.clearForeground (); + cursor.insertText(text.mid (text_index), format); // position so viewport scrolled to left cursor.movePosition (QTextCursor::StartOfLine); @@ -210,13 +262,16 @@ void DisplayText::displayDecodedText(DecodedText const& decodedText, QString con bg = color_MyCall; } auto message = decodedText.string (); + QString dxCall; + QString dxGrid; + decodedText.deCallAndGrid (dxCall, dxGrid); message = message.left (message.indexOf (QChar::Nbsp)); // strip appended info if (displayDXCCEntity && CQcall) // if enabled add the DXCC entity and B4 status to the end of the // preformated text line t1 message = appendDXCCWorkedB4 (message, decodedText.CQersCall (), &bg, logBook, color_CQ, color_DXCC, color_NewCall); - appendText (message.trimmed (), bg); + appendText (message.trimmed (), bg, decodedText.call (), dxCall); } @@ -254,3 +309,96 @@ void DisplayText::displayFoxToBeCalled(QString t, QColor bg) { appendText(t,bg); } + +namespace +{ + void update_selection (QTextCursor& cursor, QColor const& bg, QColor const& fg) + { + if (!cursor.isNull ()) + { + QTextCharFormat format {cursor.charFormat ()}; + if (bg.isValid ()) + { + format.setBackground (bg); + } + else + { + format.clearBackground (); + } + if (fg.isValid ()) + { + format.setForeground (fg); + } + else + { + format.clearForeground (); + } + cursor.mergeCharFormat (format); + } + } + + void reset_selection (QTextCursor& cursor) + { + if (!cursor.isNull ()) + { + // restore previous text format, we rely on the text + // char format at he start of the selection being the + // old one which should be the case + auto c2 = cursor; + c2.setPosition (c2.selectionStart ()); + cursor.setCharFormat (c2.charFormat ()); + } + } +} + +void DisplayText::highlight_callsign (QString const& callsign, QColor const& bg, QColor const& fg, bool last_only) +{ + QTextCharFormat old_format {currentCharFormat ()}; + QTextCursor cursor {document ()}; + if (last_only) + { + cursor.movePosition (QTextCursor::End); + cursor = document ()->find (callsign, cursor + , QTextDocument::FindBackward | QTextDocument::FindWholeWords); + if (bg.isValid () || fg.isValid ()) + { + update_selection (cursor, bg, fg); + } + else + { + reset_selection (cursor); + } + } + else + { + auto pos = highlighted_calls_.find (callsign); + if (bg.isValid () || fg.isValid ()) + { + auto colours = qMakePair (bg, fg); + if (pos == highlighted_calls_.end ()) + { + pos = highlighted_calls_.insert (callsign.toUpper (), colours); + } + else + { + pos.value () = colours; // update colours + } + while (!cursor.isNull ()) + { + cursor = document ()->find (callsign, cursor, QTextDocument::FindWholeWords); + update_selection (cursor, bg, fg); + } + } + else if (pos != highlighted_calls_.end ()) + { + highlighted_calls_.erase (pos); + QTextCursor cursor {document ()}; + while (!cursor.isNull ()) + { + cursor = document ()->find (callsign, cursor, QTextDocument::FindWholeWords); + reset_selection (cursor); + } + } + } + setCurrentCharFormat (old_format); +} diff --git a/displaytext.h b/displaytext.h index 0e027d9eb..0f1ef01b3 100644 --- a/displaytext.h +++ b/displaytext.h @@ -4,6 +4,9 @@ #include #include +#include +#include +#include #include "logbook/logbook.h" #include "decodedtext.h" @@ -30,8 +33,10 @@ public: Q_SIGNAL void selectCallsign (Qt::KeyboardModifiers); Q_SIGNAL void erased (); - Q_SLOT void appendText (QString const& text, QColor bg = Qt::white); + Q_SLOT void appendText (QString const& text, QColor bg = Qt::white + , QString const& call1 = QString {}, QString const& call2 = QString {}); Q_SLOT void erase (); + Q_SLOT void highlight_callsign (QString const& callsign, QColor const& bg, QColor const& fg, bool last_only); protected: void mouseDoubleClickEvent(QMouseEvent *e); @@ -43,6 +48,7 @@ private: QFont char_font_; QAction * erase_action_; + QHash> highlighted_calls_; }; #endif // DISPLAYTEXT_H diff --git a/mainwindow.cpp b/mainwindow.cpp index 57de7266b..2469f4c7c 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -497,6 +497,8 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple, } }); + connect (m_messageClient, &MessageClient::highlight_callsign, ui->decodedTextBrowser, &DisplayText::highlight_callsign); + // Hook up WSPR band hopping connect (ui->band_hopping_schedule_push_button, &QPushButton::clicked , &m_WSPR_band_hopping, &WSPRBandHopping::show_dialog);