mirror of
				https://github.com/saitohirga/WSJT-X.git
				synced 2025-10-30 12:30:23 -04:00 
			
		
		
		
	
		
			
	
	
		
			242 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
		
		
			
		
	
	
			242 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
|  | #include "ClientWidget.hpp"
 | ||
|  | 
 | ||
|  | #include <QRegExp>
 | ||
|  | #include <QColor>
 | ||
|  | 
 | ||
|  | namespace | ||
|  | { | ||
|  |   //QRegExp message_alphabet {"[- A-Za-z0-9+./?]*"};
 | ||
|  |   QRegExp message_alphabet {"[- @A-Za-z0-9+./?#<>]*"}; | ||
|  |   QRegularExpression cq_re {"[^A-Z0-9]*(CQ|QRZ)[^A-Z0-9]*"}; | ||
|  | 
 | ||
|  |   void update_dynamic_property (QWidget * widget, char const * property, QVariant const& value) | ||
|  |   { | ||
|  |     widget->setProperty (property, value); | ||
|  |     widget->style ()->unpolish (widget); | ||
|  |     widget->style ()->polish (widget); | ||
|  |     widget->update (); | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | ClientWidget::IdFilterModel::IdFilterModel (QString const& client_id) | ||
|  |   : client_id_ {client_id} | ||
|  |   , rx_df_ (-1) | ||
|  | { | ||
|  | } | ||
|  | 
 | ||
|  | QVariant ClientWidget::IdFilterModel::data (QModelIndex const& proxy_index, int role) const | ||
|  | { | ||
|  |   if (role == Qt::BackgroundRole) | ||
|  |     { | ||
|  |       switch (proxy_index.column ()) | ||
|  |         { | ||
|  |         case 6:                 // message
 | ||
|  |           { | ||
|  |             auto message = QSortFilterProxyModel::data (proxy_index).toString (); | ||
|  |             if (base_call_re_.pattern ().size () | ||
|  |                 && message.contains (base_call_re_)) | ||
|  |               { | ||
|  |                 return QColor {255,200,200}; | ||
|  |               } | ||
|  |             if (message.contains (cq_re)) | ||
|  |               { | ||
|  |                 return QColor {200, 255, 200}; | ||
|  |               } | ||
|  |           } | ||
|  |           break; | ||
|  | 
 | ||
|  |         case 4:                 // DF
 | ||
|  |           if (qAbs (QSortFilterProxyModel::data (proxy_index).toInt () - rx_df_) <= 10) | ||
|  |             { | ||
|  |               return QColor {255, 200, 200}; | ||
|  |             } | ||
|  |           break; | ||
|  | 
 | ||
|  |         default: | ||
|  |           break; | ||
|  |         } | ||
|  |     } | ||
|  |   return QSortFilterProxyModel::data (proxy_index, role); | ||
|  | } | ||
|  | 
 | ||
|  | bool ClientWidget::IdFilterModel::filterAcceptsRow (int source_row | ||
|  |                                                     , QModelIndex const& source_parent) const | ||
|  | { | ||
|  |   auto source_index_col0 = sourceModel ()->index (source_row, 0, source_parent); | ||
|  |   return sourceModel ()->data (source_index_col0).toString () == client_id_; | ||
|  | } | ||
|  | 
 | ||
|  | void ClientWidget::IdFilterModel::de_call (QString const& call) | ||
|  | { | ||
|  |   if (call != call_) | ||
|  |     { | ||
|  |       beginResetModel (); | ||
|  |       if (call.size ()) | ||
|  |         { | ||
|  |           base_call_re_.setPattern ("[^A-Z0-9]*" + Radio::base_callsign (call) + "[^A-Z0-9]*"); | ||
|  |         } | ||
|  |       else | ||
|  |         { | ||
|  |           base_call_re_.setPattern (QString {}); | ||
|  |         } | ||
|  |       call_ = call; | ||
|  |       endResetModel (); | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | void ClientWidget::IdFilterModel::rx_df (int df) | ||
|  | { | ||
|  |   if (df != rx_df_) | ||
|  |     { | ||
|  |       beginResetModel (); | ||
|  |       rx_df_ = df; | ||
|  |       endResetModel (); | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | ClientWidget::ClientWidget (QAbstractItemModel * decodes_model, QAbstractItemModel * beacons_model | ||
|  |                             , QString const& id, QWidget * parent) | ||
|  |   : QDockWidget {id, parent} | ||
|  |   , id_ {id} | ||
|  |   , decodes_proxy_model_ {id_} | ||
|  |   , decodes_table_view_ {new QTableView} | ||
|  |   , beacons_table_view_ {new QTableView} | ||
|  |   , message_line_edit_ {new QLineEdit} | ||
|  |   , decodes_stack_ {new QStackedLayout} | ||
|  |   , auto_off_button_ {new QPushButton {tr ("&Auto Off")}} | ||
|  |   , halt_tx_button_ {new QPushButton {tr ("&Halt Tx")}} | ||
|  |   , mode_label_ {new QLabel} | ||
|  |   , frequency_label_ {new QLabel} | ||
|  |   , rx_df_label_ {new QLabel} | ||
|  |   , tx_df_label_ {new QLabel} | ||
|  |   , report_label_ {new QLabel} | ||
|  | { | ||
|  |   // set up widgets
 | ||
|  |   decodes_proxy_model_.setSourceModel (decodes_model); | ||
|  |   decodes_table_view_->setModel (&decodes_proxy_model_); | ||
|  |   decodes_table_view_->verticalHeader ()->hide (); | ||
|  |   decodes_table_view_->hideColumn (0); | ||
|  |   decodes_table_view_->horizontalHeader ()->setStretchLastSection (true); | ||
|  | 
 | ||
|  |   auto form_layout = new QFormLayout; | ||
|  |   form_layout->addRow (tr ("Free text:"), message_line_edit_); | ||
|  |   message_line_edit_->setValidator (new QRegExpValidator {message_alphabet, this}); | ||
|  |   connect (message_line_edit_, &QLineEdit::textEdited, [this] (QString const& text) { | ||
|  |       Q_EMIT do_free_text (id_, text, false); | ||
|  |     }); | ||
|  |   connect (message_line_edit_, &QLineEdit::editingFinished, [this] () { | ||
|  |       Q_EMIT do_free_text (id_, message_line_edit_->text (), true); | ||
|  |     }); | ||
|  | 
 | ||
|  |   auto decodes_page = new QWidget; | ||
|  |   auto decodes_layout = new QVBoxLayout {decodes_page}; | ||
|  |   decodes_layout->setContentsMargins (QMargins {2, 2, 2, 2}); | ||
|  |   decodes_layout->addWidget (decodes_table_view_); | ||
|  |   decodes_layout->addLayout (form_layout); | ||
|  | 
 | ||
|  |   auto beacons_proxy_model = new IdFilterModel {id_}; | ||
|  |   beacons_proxy_model->setSourceModel (beacons_model); | ||
|  |   beacons_table_view_->setModel (beacons_proxy_model); | ||
|  |   beacons_table_view_->verticalHeader ()->hide (); | ||
|  |   beacons_table_view_->hideColumn (0); | ||
|  |   beacons_table_view_->horizontalHeader ()->setStretchLastSection (true); | ||
|  | 
 | ||
|  |   auto beacons_page = new QWidget; | ||
|  |   auto beacons_layout = new QVBoxLayout {beacons_page}; | ||
|  |   beacons_layout->setContentsMargins (QMargins {2, 2, 2, 2}); | ||
|  |   beacons_layout->addWidget (beacons_table_view_); | ||
|  | 
 | ||
|  |   decodes_stack_->addWidget (decodes_page); | ||
|  |   decodes_stack_->addWidget (beacons_page); | ||
|  | 
 | ||
|  |   // stack alternative views
 | ||
|  |   auto content_layout = new QVBoxLayout; | ||
|  |   content_layout->setContentsMargins (QMargins {2, 2, 2, 2}); | ||
|  |   content_layout->addLayout (decodes_stack_); | ||
|  | 
 | ||
|  |   // set up controls
 | ||
|  |   auto control_button_box = new QDialogButtonBox; | ||
|  |   control_button_box->addButton (auto_off_button_, QDialogButtonBox::ActionRole); | ||
|  |   control_button_box->addButton (halt_tx_button_, QDialogButtonBox::ActionRole); | ||
|  |   connect (auto_off_button_, &QAbstractButton::clicked, [this] (bool /* checked */) { | ||
|  |       Q_EMIT do_halt_tx (id_, true); | ||
|  |     }); | ||
|  |   connect (halt_tx_button_, &QAbstractButton::clicked, [this] (bool /* checked */) { | ||
|  |       Q_EMIT do_halt_tx (id_, false); | ||
|  |     }); | ||
|  |   content_layout->addWidget (control_button_box); | ||
|  | 
 | ||
|  |   // set up status area
 | ||
|  |   auto status_bar = new QStatusBar; | ||
|  |   status_bar->addPermanentWidget (mode_label_); | ||
|  |   status_bar->addPermanentWidget (frequency_label_); | ||
|  |   status_bar->addPermanentWidget (rx_df_label_); | ||
|  |   status_bar->addPermanentWidget (tx_df_label_); | ||
|  |   status_bar->addPermanentWidget (report_label_); | ||
|  |   content_layout->addWidget (status_bar); | ||
|  |   connect (this, &ClientWidget::topLevelChanged, status_bar, &QStatusBar::setSizeGripEnabled); | ||
|  | 
 | ||
|  |   // set up central widget
 | ||
|  |   auto content_widget = new QFrame; | ||
|  |   content_widget->setFrameStyle (QFrame::StyledPanel | QFrame::Sunken); | ||
|  |   content_widget->setLayout (content_layout); | ||
|  |   setWidget (content_widget); | ||
|  |   // setMinimumSize (QSize {550, 0});
 | ||
|  |   setFeatures (DockWidgetMovable | DockWidgetFloatable); | ||
|  |   setAllowedAreas (Qt::BottomDockWidgetArea); | ||
|  | 
 | ||
|  |   // 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)); | ||
|  |     }); | ||
|  | } | ||
|  | 
 | ||
|  | void ClientWidget::update_status (QString const& id, Frequency f, QString const& mode, QString const& /*dx_call*/ | ||
|  |                                   , QString const& report, QString const& tx_mode, bool tx_enabled | ||
|  |                                   , bool transmitting, bool decoding, qint32 rx_df, qint32 tx_df | ||
|  |                                   , QString const& de_call, QString const& /*de_grid*/, QString const& /*dx_grid*/) | ||
|  | { | ||
|  |   if (id == id_) | ||
|  |     { | ||
|  |       decodes_proxy_model_.de_call (de_call); | ||
|  |       decodes_proxy_model_.rx_df (rx_df); | ||
|  |       mode_label_->setText (QString {"Mode: %1%2"} | ||
|  |            .arg (mode) | ||
|  |            .arg (tx_mode.isEmpty () || tx_mode == mode ? "" : '(' + tx_mode + ')')); | ||
|  |         frequency_label_->setText ("QRG: " + Radio::pretty_frequency_MHz_string (f)); | ||
|  |         rx_df_label_->setText (rx_df >= 0 ? QString {"Rx: %1"}.arg (rx_df) : ""); | ||
|  |         tx_df_label_->setText (tx_df >= 0 ? QString {"Tx: %1"}.arg (tx_df) : ""); | ||
|  |         report_label_->setText ("SNR: " + report); | ||
|  |         update_dynamic_property (frequency_label_, "transmitting", transmitting); | ||
|  |         auto_off_button_->setEnabled (tx_enabled); | ||
|  |         halt_tx_button_->setEnabled (transmitting); | ||
|  |         update_dynamic_property (mode_label_, "decoding", decoding); | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | void ClientWidget::decode_added (bool /*is_new*/, QString const& client_id, QTime /*time*/, qint32 /*snr*/ | ||
|  |                                  , float /*delta_time*/, quint32 /*delta_frequency*/, QString const& /*mode*/ | ||
|  |                                  , QString const& /*message*/) | ||
|  | { | ||
|  |   if (client_id == id_) | ||
|  |     { | ||
|  |       decodes_stack_->setCurrentIndex (0); | ||
|  |       decodes_table_view_->resizeColumnsToContents (); | ||
|  |       decodes_table_view_->scrollToBottom (); | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | void ClientWidget::beacon_spot_added (bool /*is_new*/, QString const& client_id, QTime /*time*/, qint32 /*snr*/ | ||
|  |                                       , float /*delta_time*/, Frequency /*delta_frequency*/, qint32 /*drift*/ | ||
|  |                                       , QString const& /*callsign*/, QString const& /*grid*/, qint32 /*power*/) | ||
|  | { | ||
|  |   if (client_id == id_) | ||
|  |     { | ||
|  |       decodes_stack_->setCurrentIndex (1); | ||
|  |       beacons_table_view_->resizeColumnsToContents (); | ||
|  |       beacons_table_view_->scrollToBottom (); | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | #include "moc_ClientWidget.cpp"
 |