23 #include <Wt/Dbo/Exception> 24 #include <Wt/WApplication> 25 #include <Wt/WDoubleValidator> 26 #include <Wt/WEnvironment> 27 #include <Wt/WIntValidator> 28 #include <Wt/WLengthValidator> 30 #include <Wt/WRegExpValidator> 34 #include "dccl/protobuf/option_extensions.pb.h" 35 #include "goby/acomms/protobuf/network_ack.pb.h" 37 #include "goby/util/dynamic_protobuf_manager.h" 38 #include "goby/util/sci.h" 40 #include "liaison_commander.h" 45 using goby::common::logger_lock::lock;
46 using goby::moos::operator<<;
48 boost::mutex goby::common::LiaisonCommander::dbo_mutex_;
49 boost::shared_ptr<Dbo::backend::Sqlite3> goby::common::LiaisonCommander::sqlite3_;
50 boost::shared_ptr<Dbo::FixedSqlConnectionPool> goby::common::LiaisonCommander::connection_pool_;
51 boost::posix_time::ptime
54 const std::string MESSAGE_INCLUDE_TEXT =
"include";
55 const std::string MESSAGE_REMOVE_TEXT =
"remove";
57 const std::string STRIPE_ODD_CLASS =
"odd";
58 const std::string STRIPE_EVEN_CLASS =
"even";
60 goby::common::LiaisonCommander::LiaisonCommander(ZeroMQService* zeromq_service,
61 const protobuf::LiaisonConfig& cfg,
62 WContainerWidget* parent)
63 : LiaisonContainer(parent), MOOSNode(zeromq_service), zeromq_service_(zeromq_service),
64 pb_commander_config_(cfg.GetExtension(protobuf::pb_commander_config)),
66 commands_div_(new WStackedWidget),
67 controls_div_(new ControlsContainer(this, pb_commander_config_, commands_div_, this))
69 addWidget(commands_div_);
71 protobuf::ZeroMQServiceConfig ipc_sockets;
72 protobuf::ZeroMQServiceConfig::Socket* internal_subscribe_socket = ipc_sockets.add_socket();
73 internal_subscribe_socket->set_socket_type(protobuf::ZeroMQServiceConfig::Socket::SUBSCRIBE);
74 internal_subscribe_socket->set_socket_id(LIAISON_INTERNAL_SUBSCRIBE_SOCKET);
75 internal_subscribe_socket->set_transport(protobuf::ZeroMQServiceConfig::Socket::INPROC);
76 internal_subscribe_socket->set_connect_or_bind(protobuf::ZeroMQServiceConfig::Socket::CONNECT);
77 internal_subscribe_socket->set_socket_name(liaison_internal_publish_socket_name());
79 protobuf::ZeroMQServiceConfig::Socket* internal_publish_socket = ipc_sockets.add_socket();
80 internal_publish_socket->set_socket_type(protobuf::ZeroMQServiceConfig::Socket::PUBLISH);
81 internal_publish_socket->set_socket_id(LIAISON_INTERNAL_PUBLISH_SOCKET);
82 internal_publish_socket->set_transport(protobuf::ZeroMQServiceConfig::Socket::INPROC);
83 internal_publish_socket->set_connect_or_bind(protobuf::ZeroMQServiceConfig::Socket::CONNECT);
84 internal_publish_socket->set_socket_name(liaison_internal_subscribe_socket_name());
86 zeromq_service_->merge_cfg(ipc_sockets);
92 goby::moos::moos_technique = pb_commander_config_.moos_parser_technique();
94 for (
int i = 0, n = pb_commander_config_.subscription_size(); i < n; ++i)
96 display_subscriptions_.insert(pb_commander_config_.subscription(i));
97 subscribe(pb_commander_config_.subscription(i), LIAISON_INTERNAL_SUBSCRIBE_SOCKET);
100 if (pb_commander_config_.has_time_source_var())
101 subscribe(pb_commander_config_.time_source_var(), LIAISON_INTERNAL_SUBSCRIBE_SOCKET);
103 subscribe(pb_commander_config_.network_ack_var(), LIAISON_INTERNAL_SUBSCRIBE_SOCKET);
105 commander_timer_.setInterval(1 / cfg.update_freq() * 1.0e3);
106 commander_timer_.timeout().connect(
this, &LiaisonCommander::loop);
108 set_name(
"MOOSCommander");
111 void goby::common::LiaisonCommander::moos_inbox(
CMOOSMsg& msg)
113 glog.is(DEBUG1) &&
glog <<
"LiaisonCommander: Got message: " << msg << std::endl;
115 if (msg.GetKey() == pb_commander_config_.network_ack_var())
117 std::string value = msg.GetAsString();
121 Dbo::ptr<CommandEntry> acked_command(static_cast<goby::common::CommandEntry*>(0));
123 boost::mutex::scoped_lock slock(dbo_mutex_);
124 Dbo::Transaction transaction(controls_div_->session_);
125 acked_command = controls_div_->session_.find<CommandEntry>()
127 .bind((
long long)ack.message_time());
130 glog.is(DEBUG1) &&
glog <<
"ACKED command was of type: " 131 << acked_command->protobuf_name << std::endl;
132 protobuf::NetworkAckSet ack_set;
133 if (acked_command->acks.size())
134 ack_set.ParseFromArray(&acked_command->acks[0], acked_command->acks.size());
136 if (ack.ack_type() == goby::acomms::protobuf::NetworkAck::ACK)
137 acked_command.modify()->last_ack = ack.ack_src();
139 bool seen_ack =
false;
140 for (
int i = 0, n = ack_set.ack_size(); i < n; ++i)
142 if (ack_set.ack(i).ack_src() == ack.ack_src())
146 ack_set.add_ack()->CopyFrom(ack);
148 acked_command.modify()->acks.resize(ack_set.ByteSize());
149 ack_set.SerializeToArray(&acked_command.modify()->acks[0],
150 acked_command->acks.size());
151 transaction.commit();
157 if (display_subscriptions_.count(msg.GetKey()))
159 WContainerWidget* new_div =
new WContainerWidget(controls_div_->incoming_message_stack_);
161 new WText(
"Message: " + goby::util::as<std::string>(
162 controls_div_->incoming_message_stack_->children().size()),
166 new WGroupBox(msg.GetKey() +
" @ " +
167 boost::posix_time::to_simple_string(
168 goby::util::as<boost::posix_time::ptime>(msg.GetTime())),
171 std::string value = msg.GetAsString();
173 boost::shared_ptr<google::protobuf::Message> pb_msg = dynamic_parse_for_moos(value);
176 new WText(
"<pre>" + pb_msg->DebugString() +
"</pre>", box);
178 new WText(value, PlainText, box);
180 WPushButton* minus =
new WPushButton(
"-", new_div);
181 WPushButton* plus =
new WPushButton(
"+", new_div);
183 WPushButton*
remove =
new WPushButton(
"x", new_div);
184 remove->setFloatSide(Wt::Right);
186 plus->clicked().connect(controls_div_, &ControlsContainer::increment_incoming_messages);
187 minus->clicked().connect(controls_div_, &ControlsContainer::decrement_incoming_messages);
188 remove->clicked().connect(controls_div_, &ControlsContainer::remove_incoming_message);
190 controls_div_->incoming_message_stack_->setCurrentIndex(
191 controls_div_->incoming_message_stack_->children().size() - 1);
195 void goby::common::LiaisonCommander::ControlsContainer::increment_incoming_messages(
196 const WMouseEvent&
event)
198 int new_index = incoming_message_stack_->currentIndex() + 1;
199 if (new_index == static_cast<int>(incoming_message_stack_->children().size()))
202 incoming_message_stack_->setCurrentIndex(new_index);
205 void goby::common::LiaisonCommander::ControlsContainer::decrement_incoming_messages(
206 const WMouseEvent& event)
208 int new_index =
static_cast<int>(incoming_message_stack_->currentIndex()) - 1;
210 new_index = incoming_message_stack_->children().size() - 1;
212 incoming_message_stack_->setCurrentIndex(new_index);
215 void goby::common::LiaisonCommander::ControlsContainer::remove_incoming_message(
216 const WMouseEvent& event)
218 WWidget*
remove = incoming_message_stack_->currentWidget();
219 decrement_incoming_messages(event);
220 incoming_message_stack_->removeWidget(
remove);
223 void goby::common::LiaisonCommander::loop()
225 ControlsContainer::CommandContainer* current_command =
226 dynamic_cast<ControlsContainer::CommandContainer*
>(
227 controls_div_->commands_div_->currentWidget());
229 while (zeromq_service_->poll(0)) {}
231 if (current_command && current_command->time_fields_.size())
233 for (std::map<Wt::WFormWidget*, const google::protobuf::FieldDescriptor*>::iterator
234 it = current_command->time_fields_.begin(),
235 end = current_command->time_fields_.end();
237 current_command->set_time_field(it->first, it->second);
240 if (current_command && (last_db_update_time_ > current_command->last_reload_time_))
242 glog.is(DEBUG1) &&
glog <<
"Reloading command!" << std::endl;
243 glog.is(DEBUG1) &&
glog << last_db_update_time_ <<
"/" << current_command->last_reload_time_
246 boost::mutex::scoped_lock slock(dbo_mutex_);
247 Dbo::Transaction transaction(controls_div_->session_);
248 current_command->query_model_->reload();
253 goby::common::LiaisonCommander::ControlsContainer::ControlsContainer(
254 MOOSNode* moos_node,
const protobuf::ProtobufCommanderConfig& pb_commander_config,
255 WStackedWidget* commands_div, WContainerWidget* parent )
256 : WGroupBox(
"Controls", parent), moos_node_(moos_node),
257 pb_commander_config_(pb_commander_config), command_label_(new WLabel(
"Message: ", this)),
258 command_selection_(new WComboBox(this)), buttons_div_(new WContainerWidget(this)),
259 comment_label_(new WLabel(
"Log comment: ", buttons_div_)),
260 comment_line_(new WLineEdit(buttons_div_)),
261 send_button_(new WPushButton(
"Send", buttons_div_)),
262 clear_button_(new WPushButton(
"Clear", buttons_div_)), commands_div_(commands_div),
264 incoming_message_stack_(new
Wt::WStackedWidget(this))
271 boost::mutex::scoped_lock slock(dbo_mutex_);
272 sqlite3_.reset(
new Dbo::backend::Sqlite3(pb_commander_config_.sqlite3_database()));
273 connection_pool_.reset(
new Dbo::FixedSqlConnectionPool(
274 sqlite3_.get(), pb_commander_config_.database_pool_size()));
278 boost::mutex::scoped_lock slock(dbo_mutex_);
279 session_.setConnectionPool(*connection_pool_);
280 session_.mapClass<CommandEntry>(
"_liaison_commands");
284 session_.createTables();
286 catch (Dbo::Exception& e)
288 glog.is(VERBOSE) &&
glog <<
"Could not create tables: " << e.what() << std::endl;
299 incoming_message_stack_->addStyleClass(
"fixed-left");
315 send_button_->setDisabled(
true);
316 clear_button_->setDisabled(
true);
317 comment_line_->setDisabled(
true);
319 comment_label_->setBuddy(comment_line_);
321 command_selection_->addItem(
"(Select a command message)");
322 send_button_->clicked().connect(
this, &ControlsContainer::send_message);
323 clear_button_->clicked().connect(
this, &ControlsContainer::clear_message);
325 Dbo::ptr<CommandEntry> last_command(static_cast<goby::common::CommandEntry*>(0));
327 boost::mutex::scoped_lock slock(dbo_mutex_);
328 Dbo::Transaction transaction(session_);
329 last_command = session_.find<CommandEntry>(
"ORDER BY time DESC LIMIT 1");
331 glog.is(DEBUG1) &&
glog <<
"Last command was of type: " << last_command->protobuf_name
335 command_selection_->activated().connect(
this, &ControlsContainer::switch_command);
337 for (
int i = 0, n = pb_commander_config.load_protobuf_name_size(); i < n; ++i)
339 const google::protobuf::Descriptor* desc =
340 goby::util::DynamicProtobufManager::find_descriptor(
341 pb_commander_config.load_protobuf_name(i));
346 glog <<
"Could not find protobuf name " << pb_commander_config.load_protobuf_name(i)
347 <<
" to load for Protobuf Commander (configuration line `load_protobuf_name`)" 352 command_selection_->addItem(pb_commander_config.load_protobuf_name(i));
356 command_selection_->model()->sort(0);
360 int last_command_index = command_selection_->findText(last_command->protobuf_name);
361 if (last_command_index >= 0)
363 command_selection_->setCurrentIndex(last_command_index);
364 switch_command(command_selection_->currentIndex());
369 void goby::common::LiaisonCommander::ControlsContainer::switch_command(
int selection_index)
371 if (selection_index == 0)
373 send_button_->setDisabled(
true);
374 clear_button_->setDisabled(
true);
375 comment_line_->setDisabled(
true);
379 send_button_->setDisabled(
false);
380 clear_button_->setDisabled(
false);
381 comment_line_->setDisabled(
false);
383 std::string protobuf_name = command_selection_->itemText(selection_index).narrow();
385 if (!commands_.count(protobuf_name))
387 CommandContainer* new_command =
388 new CommandContainer(moos_node_, pb_commander_config_, protobuf_name, &session_);
391 commands_div_->addWidget(new_command);
393 commands_[protobuf_name] = commands_div_->count() - 1;
395 commands_div_->setCurrentIndex(commands_[protobuf_name]);
399 void goby::common::LiaisonCommander::ControlsContainer::clear_message()
401 WDialog dialog(
"Confirm clearing of message: " + command_selection_->currentText());
402 WPushButton ok(
"Clear", dialog.contents());
403 WPushButton cancel(
"Cancel", dialog.contents());
405 dialog.rejectWhenEscapePressed();
406 ok.clicked().connect(&dialog, &WDialog::accept);
407 cancel.clicked().connect(&dialog, &WDialog::reject);
409 if (dialog.exec() == WDialog::Accepted)
411 CommandContainer* current_command =
412 dynamic_cast<CommandContainer*
>(commands_div_->currentWidget());
413 current_command->message_->Clear();
414 current_command->generate_root();
418 void goby::common::LiaisonCommander::ControlsContainer::send_message()
420 glog.is(VERBOSE) &&
glog <<
"Message to be sent!" << std::endl;
422 WDialog dialog(
"Confirm sending of message: " + command_selection_->currentText());
424 CommandContainer* current_command =
425 dynamic_cast<CommandContainer*
>(commands_div_->currentWidget());
427 WGroupBox* comment_box =
new WGroupBox(
"Log comment", dialog.contents());
428 WLineEdit* comment_line =
new WLineEdit(comment_box);
429 comment_line->setText(comment_line_->text());
431 WGroupBox* message_box =
new WGroupBox(
"Message to send", dialog.contents());
432 WContainerWidget* message_div =
new WContainerWidget(message_box);
434 new WText(
"<pre>" + current_command->message_->DebugString() +
"</pre>", message_div);
436 message_div->setMaximumSize(pb_commander_config_.modal_dimensions().width(),
437 pb_commander_config_.modal_dimensions().height());
438 message_div->setOverflow(WContainerWidget::OverflowAuto);
442 WPushButton ok(
"Send", dialog.contents());
443 WPushButton cancel(
"Cancel", dialog.contents());
445 dialog.rejectWhenEscapePressed();
447 ok.clicked().connect(&dialog, &WDialog::accept);
448 cancel.clicked().connect(&dialog, &WDialog::reject);
450 if (dialog.exec() == WDialog::Accepted)
452 std::string serialized;
453 serialize_for_moos(&serialized, *current_command->message_);
454 moos_node_->send(
CMOOSMsg(MOOS_NOTIFY,
"LIAISON_COMMANDER_OUT", serialized),
455 LIAISON_INTERNAL_PUBLISH_SOCKET);
457 CommandEntry* command_entry =
new CommandEntry;
458 command_entry->protobuf_name = current_command->message_->GetDescriptor()->full_name();
459 command_entry->bytes.resize(current_command->message_->ByteSize());
460 current_command->message_->SerializeToArray(&command_entry->bytes[0],
461 command_entry->bytes.size());
462 command_entry->address = wApp->environment().clientAddress();
465 command_entry->time.setPosixTime(now);
466 command_entry->utime = current_command->latest_time_;
468 command_entry->comment = comment_line->text().narrow();
469 command_entry->last_ack = 0;
470 session_.add(command_entry);
473 boost::mutex::scoped_lock slock(dbo_mutex_);
474 Dbo::Transaction transaction(*current_command->session_);
475 transaction.commit();
476 last_db_update_time_ = now;
479 comment_line_->setText(
"");
480 current_command->query_model_->reload();
484 goby::common::LiaisonCommander::ControlsContainer::CommandContainer::CommandContainer(
485 MOOSNode* moos_node,
const protobuf::ProtobufCommanderConfig& pb_commander_config,
486 const std::string& protobuf_name, Dbo::Session* session)
488 : WGroupBox(protobuf_name), moos_node_(moos_node),
489 message_(
goby::util::DynamicProtobufManager::new_protobuf_message(protobuf_name)),
490 latest_time_(0), tree_box_(new WGroupBox(
"Contents", this)),
491 tree_table_(new WTreeTable(tree_box_)),
493 session_(session), query_model_(new Dbo::QueryModel<Dbo::ptr<CommandEntry> >(this)),
494 query_box_(new WGroupBox(
"Sent message log (click for details)", this)),
495 query_table_(new WTreeView(query_box_)), last_reload_time_(
boost::posix_time::neg_infin),
496 pb_commander_config_(pb_commander_config)
501 tree_table_->addColumn(
"Value", pb_commander_config.value_width_pixels());
502 tree_table_->addColumn(
"Modify", pb_commander_config.modify_width_pixels());
505 boost::mutex::scoped_lock slock(dbo_mutex_);
506 Dbo::Transaction transaction(*session_);
507 query_model_->setQuery(
508 session_->find<CommandEntry>(
"where protobuf_name='" + protobuf_name +
"'"));
511 query_model_->addColumn(
"comment",
"Comment");
512 query_model_->addColumn(
"protobuf_name",
"Name");
513 query_model_->addColumn(
"address",
"Network Address");
514 query_model_->addColumn(
"time",
"Time");
515 query_model_->addColumn(
"last_ack",
"Latest Ack");
517 query_table_->setModel(query_model_);
518 query_table_->resize(WLength::Auto, pb_commander_config.database_view_height());
519 query_table_->sortByColumn(protobuf::ProtobufCommanderConfig::COLUMN_TIME, DescendingOrder);
520 query_table_->setMinimumSize(pb_commander_config.database_width().comment_width() +
521 pb_commander_config.database_width().name_width() +
522 pb_commander_config.database_width().ip_width() +
523 pb_commander_config.database_width().time_width() +
524 pb_commander_config.database_width().last_ack_width() +
525 7 * (protobuf::MOOSScopeConfig::COLUMN_MAX + 1),
528 query_table_->setColumnWidth(protobuf::ProtobufCommanderConfig::COLUMN_COMMENT,
529 pb_commander_config.database_width().comment_width());
530 query_table_->setColumnWidth(protobuf::ProtobufCommanderConfig::COLUMN_NAME,
531 pb_commander_config.database_width().name_width());
533 query_table_->setColumnWidth(protobuf::ProtobufCommanderConfig::COLUMN_IP,
534 pb_commander_config.database_width().ip_width());
536 query_table_->setColumnWidth(protobuf::ProtobufCommanderConfig::COLUMN_TIME,
537 pb_commander_config.database_width().time_width());
539 query_table_->setColumnWidth(protobuf::ProtobufCommanderConfig::COLUMN_LAST_ACK,
540 pb_commander_config.database_width().last_ack_width());
542 query_table_->clicked().connect(
this, &CommandContainer::handle_database_double_click);
544 if (query_model_->rowCount() > 0)
546 const Dbo::ptr<CommandEntry>& entry = query_model_->resultRow(0);
547 message_->ParseFromArray(&entry->bytes[0], entry->bytes.size());
550 glog.is(DEBUG1) &&
glog <<
"Model has " << query_model_->rowCount() <<
" rows" << std::endl;
555 void goby::common::LiaisonCommander::ControlsContainer::CommandContainer::
556 handle_database_double_click(
const WModelIndex& index,
const WMouseEvent& event)
558 glog.is(DEBUG1) &&
glog <<
"clicked: " << index.row() <<
"," << index.column() << std::endl;
560 const Dbo::ptr<CommandEntry>& entry = query_model_->resultRow(index.row());
562 boost::shared_ptr<google::protobuf::Message> message(message_->New());
563 message->ParseFromArray(&entry->bytes[0], entry->bytes.size());
567 glog.is(WARN) &&
glog <<
"Invalid message!" << std::endl;
571 database_dialog_.reset(
new WDialog(
"Viewing log entry: " + entry->protobuf_name +
572 " posted at " + entry->time.toString()));
574 WGroupBox* comment_box =
new WGroupBox(
"Log comment", database_dialog_->contents());
575 new WText(entry->comment, comment_box);
577 WContainerWidget* contents_div =
new WContainerWidget(database_dialog_->contents());
578 WGroupBox* message_box =
new WGroupBox(
"Message posted", contents_div);
580 WContainerWidget* message_div =
new WContainerWidget(message_box);
582 new WText(
"<pre>" + message->DebugString() +
"</pre>", message_div);
584 protobuf::NetworkAckSet acks;
585 acks.ParseFromArray(&entry->acks[0], entry->acks.size());
587 WGroupBox* acks_box =
new WGroupBox(
"Acks posted", contents_div);
588 WContainerWidget* acks_div =
new WContainerWidget(acks_box);
589 new WText(
"<pre>" + acks.DebugString() +
"</pre>", acks_div);
591 contents_div->setMaximumSize(pb_commander_config_.modal_dimensions().width(),
592 pb_commander_config_.modal_dimensions().height());
593 contents_div->setOverflow(WContainerWidget::OverflowAuto);
595 WPushButton* edit =
new WPushButton(
"Edit (replace)", database_dialog_->contents());
596 WPushButton* merge =
new WPushButton(
"Edit (merge)", database_dialog_->contents());
597 WPushButton* cancel =
new WPushButton(
"Cancel", database_dialog_->contents());
599 database_dialog_->rejectWhenEscapePressed();
601 edit->clicked().connect(
602 boost::bind(&CommandContainer::handle_database_dialog,
this, RESPONSE_EDIT, message));
603 merge->clicked().connect(
604 boost::bind(&CommandContainer::handle_database_dialog,
this, RESPONSE_MERGE, message));
605 cancel->clicked().connect(
606 boost::bind(&CommandContainer::handle_database_dialog,
this, RESPONSE_CANCEL, message));
608 database_dialog_->show();
613 void goby::common::LiaisonCommander::ControlsContainer::CommandContainer::handle_database_dialog(
614 DatabaseDialogResponse response, boost::shared_ptr<google::protobuf::Message> message)
619 message_->CopyFrom(*message);
621 database_dialog_->accept();
625 message->MergeFrom(*message_);
626 message_->CopyFrom(*message);
628 database_dialog_->accept();
631 case RESPONSE_CANCEL: database_dialog_->reject();
break;
635 void goby::common::LiaisonCommander::ControlsContainer::CommandContainer::generate_root()
637 const google::protobuf::Descriptor* desc = message_->GetDescriptor();
640 WTreeTableNode* root =
new WTreeTableNode(desc->name());
641 root->setImagePack(
"resources/");
642 root->setStyleClass(STRIPE_EVEN_CLASS);
645 tree_table_->setTreeRoot(root,
"Field");
647 time_fields_.clear();
649 generate_tree(root, message_.get());
654 void goby::common::LiaisonCommander::ControlsContainer::CommandContainer::generate_tree(
657 const google::protobuf::Descriptor* desc = message->GetDescriptor();
659 for (
int i = 0, n = desc->field_count(); i < n; ++i)
660 generate_tree_row(parent, message, desc->field(i));
662 std::vector<const google::protobuf::FieldDescriptor*> extensions;
663 goby::util::DynamicProtobufManager::user_descriptor_pool().FindAllExtensions(desc, &extensions);
664 google::protobuf::DescriptorPool::generated_pool()->FindAllExtensions(desc, &extensions);
665 for (
int i = 0, n = extensions.size(); i < n; ++i)
666 generate_tree_row(parent, message, extensions[i]);
669 void goby::common::LiaisonCommander::ControlsContainer::CommandContainer::generate_tree_row(
671 const google::protobuf::FieldDescriptor* field_desc)
673 const google::protobuf::Reflection* refl = message->GetReflection();
675 if (field_desc->options().GetExtension(dccl::field).omit())
678 int index = parent->childNodes().size();
680 LiaisonTreeTableNode* node =
681 new LiaisonTreeTableNode(field_desc->is_extension() ?
"[" + field_desc->full_name() +
"]: " 682 : field_desc->name() +
": ",
685 if ((parent->styleClass() == STRIPE_ODD_CLASS && index % 2) ||
686 (parent->styleClass() == STRIPE_EVEN_CLASS && !(index % 2)))
687 node->setStyleClass(STRIPE_ODD_CLASS);
689 node->setStyleClass(STRIPE_EVEN_CLASS);
691 WFormWidget* value_field = 0;
692 WFormWidget* modify_field = 0;
693 if (field_desc->is_repeated())
697 WSpinBox* spin_box =
new WSpinBox;
698 spin_box->setTextSize(3);
700 spin_box->setRange(0, std::numeric_limits<int>::max());
701 spin_box->setSingleStep(1);
703 spin_box->valueChanged().connect(boost::bind(&CommandContainer::handle_repeated_size_change,
704 this, _1, message, field_desc, node));
706 spin_box->setValue(refl->FieldSize(*message, field_desc));
707 spin_box->valueChanged().emit(refl->FieldSize(*message, field_desc));
709 modify_field = spin_box;
713 if (field_desc->cpp_type() == google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE)
715 if (field_desc->is_required())
717 generate_tree(node, message->GetReflection()->MutableMessage(message, field_desc));
722 WPushButton* button =
new WPushButton(MESSAGE_INCLUDE_TEXT);
724 button->clicked().connect(
725 boost::bind(&CommandContainer::handle_toggle_single_message,
this, _1, message,
726 field_desc, button, node));
728 if (refl->HasField(*message, field_desc))
731 handle_toggle_single_message(WMouseEvent(), message, field_desc, button, node);
734 modify_field = button;
739 generate_tree_field(value_field, message, field_desc);
743 node->setColumnWidget(1, value_field);
747 dccl_default_modify_field(modify_field, field_desc);
749 generate_field_info_box(modify_field, field_desc);
751 node->setColumnWidget(2, modify_field);
755 void goby::common::LiaisonCommander::ControlsContainer::CommandContainer::generate_tree_field(
757 const google::protobuf::FieldDescriptor* field_desc,
int index )
759 const google::protobuf::Reflection* refl = message->GetReflection();
761 switch (field_desc->cpp_type())
763 case google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE:
break;
765 case google::protobuf::FieldDescriptor::CPPTYPE_INT32:
767 WIntValidator* validator =
new WIntValidator;
769 if (field_desc->is_repeated() && refl->FieldSize(*message, field_desc) <= index)
770 refl->AddInt32(message, field_desc, field_desc->default_value_int32());
772 int32 value = field_desc->is_repeated()
773 ? refl->GetRepeatedInt32(*message, field_desc, index)
774 : refl->GetInt32(*message, field_desc);
776 value_field = generate_single_line_edit_field(
777 message, field_desc, goby::util::as<std::string>(value),
778 goby::util::as<std::string>(field_desc->default_value_int32()), validator, index);
782 case google::protobuf::FieldDescriptor::CPPTYPE_INT64:
784 WIntValidator* validator = 0;
786 if (field_desc->is_repeated() && refl->FieldSize(*message, field_desc) <= index)
787 refl->AddInt64(message, field_desc, field_desc->default_value_int64());
789 int64 value = field_desc->is_repeated()
790 ? refl->GetRepeatedInt64(*message, field_desc, index)
791 : refl->GetInt64(*message, field_desc);
793 value_field = generate_single_line_edit_field(
794 message, field_desc, goby::util::as<std::string>(value),
795 goby::util::as<std::string>(field_desc->default_value_int64()), validator, index);
799 case google::protobuf::FieldDescriptor::CPPTYPE_UINT32:
801 WIntValidator* validator =
new WIntValidator;
802 validator->setBottom(0);
804 if (field_desc->is_repeated() && refl->FieldSize(*message, field_desc) <= index)
805 refl->AddUInt32(message, field_desc, field_desc->default_value_uint32());
807 uint32 value = field_desc->is_repeated()
808 ? refl->GetRepeatedUInt32(*message, field_desc, index)
809 : refl->GetUInt32(*message, field_desc);
811 value_field = generate_single_line_edit_field(
812 message, field_desc, goby::util::as<std::string>(value),
813 goby::util::as<std::string>(field_desc->default_value_uint32()), validator, index);
817 case google::protobuf::FieldDescriptor::CPPTYPE_UINT64:
819 WIntValidator* validator = 0;
821 if (field_desc->is_repeated() && refl->FieldSize(*message, field_desc) <= index)
822 refl->AddUInt64(message, field_desc, field_desc->default_value_uint64());
824 uint64 value = field_desc->is_repeated()
825 ? refl->GetRepeatedUInt64(*message, field_desc, index)
826 : refl->GetUInt64(*message, field_desc);
828 value_field = generate_single_line_edit_field(
829 message, field_desc, goby::util::as<std::string>(value),
830 goby::util::as<std::string>(field_desc->default_value_uint64()), validator, index);
834 case google::protobuf::FieldDescriptor::CPPTYPE_STRING:
836 if (field_desc->is_repeated() && refl->FieldSize(*message, field_desc) <= index)
837 refl->AddString(message, field_desc, field_desc->default_value_string());
839 WValidator* validator = 0;
841 std::string current_str = field_desc->is_repeated()
842 ? refl->GetRepeatedString(*message, field_desc, index)
843 : refl->GetString(*message, field_desc);
844 std::string default_str = field_desc->default_value_string();
846 if (field_desc->type() == google::protobuf::FieldDescriptor::TYPE_BYTES)
848 validator =
new WRegExpValidator(
"([0-9,a-f,A-F][0-9,a-f,A-F])*");
850 current_str = goby::util::hex_encode(current_str);
851 default_str = goby::util::hex_encode(default_str);
855 validator =
new WValidator;
858 value_field = generate_single_line_edit_field(message, field_desc, current_str,
859 default_str, validator, index);
863 case google::protobuf::FieldDescriptor::CPPTYPE_FLOAT:
865 if (field_desc->is_repeated() && refl->FieldSize(*message, field_desc) <= index)
866 refl->AddFloat(message, field_desc, field_desc->default_value_float());
868 float value = field_desc->is_repeated()
869 ? refl->GetRepeatedFloat(*message, field_desc, index)
870 : refl->GetFloat(*message, field_desc);
872 WDoubleValidator* validator =
new WDoubleValidator;
873 validator->setRange(std::numeric_limits<float>::min(),
874 std::numeric_limits<float>::max());
876 value_field = generate_single_line_edit_field(
878 goby::util::as<std::string>(value, std::numeric_limits<float>::digits10),
879 goby::util::as<std::string>(field_desc->default_value_float(),
880 std::numeric_limits<float>::digits10),
885 case google::protobuf::FieldDescriptor::CPPTYPE_DOUBLE:
887 if (field_desc->is_repeated() && refl->FieldSize(*message, field_desc) <= index)
888 refl->AddDouble(message, field_desc, field_desc->default_value_double());
890 double value = field_desc->is_repeated()
891 ? refl->GetRepeatedDouble(*message, field_desc, index)
892 : refl->GetDouble(*message, field_desc);
894 WDoubleValidator* validator =
new WDoubleValidator;
896 value_field = generate_single_line_edit_field(
898 goby::util::as<std::string>(value, std::numeric_limits<double>::digits10),
899 goby::util::as<std::string>(field_desc->default_value_double(),
900 std::numeric_limits<double>::digits10),
905 case google::protobuf::FieldDescriptor::CPPTYPE_BOOL:
907 if (field_desc->is_repeated() && refl->FieldSize(*message, field_desc) <= index)
908 refl->AddBool(message, field_desc, field_desc->default_value_bool());
910 bool value = field_desc->is_repeated()
911 ? refl->GetRepeatedBool(*message, field_desc, index)
912 : refl->GetBool(*message, field_desc);
914 std::vector<WString> strings;
915 strings.push_back(
"true");
916 strings.push_back(
"false");
918 value_field = generate_combo_box_field(
919 message, field_desc, strings, value ? 0 : 1,
920 goby::util::as<std::string>(field_desc->default_value_bool()), index);
924 case google::protobuf::FieldDescriptor::CPPTYPE_ENUM:
926 if (field_desc->is_repeated() && refl->FieldSize(*message, field_desc) <= index)
927 refl->AddEnum(message, field_desc, field_desc->default_value_enum());
929 const google::protobuf::EnumValueDescriptor* value =
930 field_desc->is_repeated() ? refl->GetRepeatedEnum(*message, field_desc, index)
931 : refl->GetEnum(*message, field_desc);
933 std::vector<WString> strings;
935 const google::protobuf::EnumDescriptor* enum_desc = field_desc->enum_type();
937 for (
int i = 0, n = enum_desc->value_count(); i < n; ++i)
938 strings.push_back(enum_desc->value(i)->name());
940 value_field = generate_combo_box_field(
941 message, field_desc, strings, value->index(),
942 goby::util::as<std::string>(field_desc->default_value_enum()->name()), index);
947 dccl_default_value_field(value_field, field_desc);
950 generate_field_info_box(value_field, field_desc);
952 void goby::common::LiaisonCommander::ControlsContainer::CommandContainer::generate_field_info_box(
953 Wt::WFormWidget*& value_field,
const google::protobuf::FieldDescriptor* field_desc)
963 std::vector<const google::protobuf::FieldDescriptor*> extensions;
964 google::protobuf::DescriptorPool::generated_pool()->FindAllExtensions(
965 field_desc->options().GetDescriptor(), &extensions);
966 for (
int i = 0, n = extensions.size(); i < n; ++i)
968 const google::protobuf::FieldDescriptor* ext_field_desc = extensions[i];
969 if (!ext_field_desc->is_repeated() &&
970 field_desc->options().GetReflection()->HasField(field_desc->options(), ext_field_desc))
973 google::protobuf::TextFormat::PrintFieldValueToString(field_desc->options(),
974 ext_field_desc, -1, &ext_str);
979 info +=
"[Options] " + ext_field_desc->full_name() +
": " + ext_str;
1001 void goby::common::LiaisonCommander::ControlsContainer::CommandContainer::handle_line_field_changed(
1003 WLineEdit* field,
int index)
1005 std::string value = field->text().narrow();
1007 const google::protobuf::Reflection* refl = message->GetReflection();
1009 if (value.empty() && field_desc->is_repeated())
1010 value = field->emptyText().narrow();
1012 if (value.empty() && !field_desc->is_repeated())
1014 refl->ClearField(message, field_desc);
1018 switch (field_desc->cpp_type())
1020 case google::protobuf::FieldDescriptor::CPPTYPE_INT32:
1021 field_desc->is_repeated()
1022 ? refl->SetRepeatedInt32(message, field_desc, index,
1023 goby::util::as<int32>(value))
1024 : refl->SetInt32(message, field_desc, goby::util::as<int32>(value));
1027 case google::protobuf::FieldDescriptor::CPPTYPE_INT64:
1028 field_desc->is_repeated()
1029 ? refl->SetRepeatedInt64(message, field_desc, index,
1030 goby::util::as<int64>(value))
1031 : refl->SetInt64(message, field_desc, goby::util::as<int64>(value));
1034 case google::protobuf::FieldDescriptor::CPPTYPE_UINT32:
1035 field_desc->is_repeated()
1036 ? refl->SetRepeatedUInt32(message, field_desc, index,
1037 goby::util::as<uint32>(value))
1038 : refl->SetUInt32(message, field_desc, goby::util::as<uint32>(value));
1041 case google::protobuf::FieldDescriptor::CPPTYPE_UINT64:
1042 field_desc->is_repeated()
1043 ? refl->SetRepeatedUInt64(message, field_desc, index,
1044 goby::util::as<uint64>(value))
1045 : refl->SetUInt64(message, field_desc, goby::util::as<uint64>(value));
1048 case google::protobuf::FieldDescriptor::CPPTYPE_STRING:
1050 if (field_desc->type() == google::protobuf::FieldDescriptor::TYPE_BYTES)
1052 value = goby::util::hex_decode(value);
1055 field_desc->is_repeated()
1056 ? refl->SetRepeatedString(message, field_desc, index, value)
1057 : refl->SetString(message, field_desc, value);
1060 case google::protobuf::FieldDescriptor::CPPTYPE_FLOAT:
1062 double fvalue = goby::util::as<float>(value);
1064 if (field_desc->options().GetExtension(dccl::field).has_precision())
1065 field->setText(string_from_dccl_double(&fvalue, field_desc));
1067 field_desc->is_repeated()
1068 ? refl->SetRepeatedFloat(message, field_desc, index, fvalue)
1069 : refl->SetFloat(message, field_desc, fvalue);
1073 case google::protobuf::FieldDescriptor::CPPTYPE_DOUBLE:
1075 double dvalue = goby::util::as<double>(value);
1077 if (field_desc->options().GetExtension(dccl::field).has_precision())
1078 field->setText(string_from_dccl_double(&dvalue, field_desc));
1080 field_desc->is_repeated()
1081 ? refl->SetRepeatedDouble(message, field_desc, index, dvalue)
1082 : refl->SetDouble(message, field_desc, dvalue);
1091 void goby::common::LiaisonCommander::ControlsContainer::CommandContainer::
1093 const google::protobuf::FieldDescriptor* field_desc,
1094 WComboBox* field,
int index)
1096 const google::protobuf::Reflection* refl = message->GetReflection();
1098 if (field->currentIndex() == 0)
1099 refl->ClearField(message, field_desc);
1102 std::string value = field->currentText().narrow();
1104 switch (field_desc->cpp_type())
1106 case google::protobuf::FieldDescriptor::CPPTYPE_BOOL:
1107 field_desc->is_repeated()
1108 ? refl->SetRepeatedBool(message, field_desc, index, goby::util::as<bool>(value))
1109 : refl->SetBool(message, field_desc, goby::util::as<bool>(value));
1112 case google::protobuf::FieldDescriptor::CPPTYPE_ENUM:
1113 field_desc->is_repeated()
1114 ? refl->SetRepeatedEnum(message, field_desc, index,
1115 field_desc->enum_type()->FindValueByName(value))
1116 : refl->SetEnum(message, field_desc,
1117 field_desc->enum_type()->FindValueByName(value));
1123 glog.is(DEBUG1) &&
glog <<
"The message is: " << message_->DebugString() << std::endl;
1126 WLineEdit* goby::common::LiaisonCommander::ControlsContainer::CommandContainer::
1128 const google::protobuf::FieldDescriptor* field_desc,
1129 const std::string& current_value,
1130 const std::string& default_value,
1131 WValidator* validator ,
int index )
1133 const google::protobuf::Reflection* refl = message->GetReflection();
1135 WLineEdit* line_edit =
new WLineEdit();
1137 if (field_desc->has_default_value() || field_desc->is_repeated())
1138 line_edit->setEmptyText(default_value);
1140 if ((!field_desc->is_repeated() && refl->HasField(*message, field_desc)) ||
1141 (field_desc->is_repeated() && index < refl->FieldSize(*message, field_desc)))
1142 line_edit->setText(current_value);
1146 validator->setMandatory(field_desc->is_required());
1147 line_edit->setValidator(validator);
1150 line_edit->changed().connect(boost::bind(&CommandContainer::handle_line_field_changed,
this,
1151 message, field_desc, line_edit, index));
1157 goby::common::LiaisonCommander::ControlsContainer::CommandContainer::generate_combo_box_field(
1159 const std::vector<WString>& strings,
int current_value,
const std::string& default_value,
1162 const google::protobuf::Reflection* refl = message->GetReflection();
1164 WComboBox* combo_box =
new WComboBox;
1165 WStringListModel* model =
new WStringListModel(strings,
this);
1167 if (field_desc->has_default_value())
1168 model->insertString(0,
"(default: " + default_value +
")");
1170 model->insertString(0,
"");
1172 combo_box->setModel(model);
1174 if ((!field_desc->is_repeated() && refl->HasField(*message, field_desc)) ||
1175 (field_desc->is_repeated() && index < refl->FieldSize(*message, field_desc)))
1176 combo_box->setCurrentIndex(current_value + 1);
1178 combo_box->changed().connect(boost::bind(&CommandContainer::handle_combo_field_changed,
this,
1179 message, field_desc, combo_box, index));
1208 void goby::common::LiaisonCommander::ControlsContainer::CommandContainer::set_time_field(
1209 WFormWidget* value_field,
const google::protobuf::FieldDescriptor* field_desc)
1211 if (WLineEdit* line_edit = dynamic_cast<WLineEdit*>(value_field))
1213 boost::posix_time::ptime now;
1214 if (pb_commander_config_.has_time_source_var())
1216 CMOOSMsg& newest = moos_node_->newest(pb_commander_config_.time_source_var());
1225 const dccl::DCCLFieldOptions& options = field_desc->options().GetExtension(dccl::field);
1226 latest_time_ = goby::util::as<uint64>(now);
1229 MICROSEC_ORDER_MAG = 6
1232 switch (field_desc->cpp_type())
1234 default: line_edit->setText(
"Error: invalid goby-acomms time type");
break;
1236 case google::protobuf::FieldDescriptor::CPPTYPE_UINT64:
1237 case google::protobuf::FieldDescriptor::CPPTYPE_INT64:
1238 line_edit->setText(goby::util::as<std::string>(goby::util::as<uint64>(now)));
1239 if (!options.has_precision())
1240 latest_time_ = dccl::round(latest_time_, -MICROSEC_ORDER_MAG);
1242 latest_time_ = dccl::round(latest_time_, options.precision());
1245 case google::protobuf::FieldDescriptor::CPPTYPE_STRING:
1246 line_edit->setText(goby::util::as<std::string>(now));
1249 case google::protobuf::FieldDescriptor::CPPTYPE_DOUBLE:
1250 line_edit->setText(goby::util::as<std::string>(
1251 goby::util::unbiased_round(goby::util::as<double>(now), 0)));
1253 latest_time_ = dccl::round(latest_time_, options.precision() - MICROSEC_ORDER_MAG);
1257 line_edit->changed().emit();
1261 void goby::common::LiaisonCommander::ControlsContainer::CommandContainer::dccl_default_value_field(
1262 WFormWidget*& value_field,
const google::protobuf::FieldDescriptor* field_desc)
1264 const dccl::DCCLFieldOptions& options = field_desc->options().GetExtension(dccl::field);
1266 if (options.has_min() && options.has_max())
1268 WValidator* validator = value_field->validator();
1269 if (WIntValidator* int_validator = dynamic_cast<WIntValidator*>(validator))
1270 int_validator->setRange(options.min(), options.max());
1271 if (WDoubleValidator* double_validator = dynamic_cast<WDoubleValidator*>(validator))
1272 double_validator->setRange(options.min(), options.max());
1275 if (options.has_static_value())
1277 if (WLineEdit* line_edit = dynamic_cast<WLineEdit*>(value_field))
1279 line_edit->setText(options.static_value());
1280 line_edit->changed().emit();
1283 else if (WComboBox* combo_box = dynamic_cast<WComboBox*>(value_field))
1285 combo_box->setCurrentIndex(combo_box->findText(options.static_value()));
1286 combo_box->changed().emit();
1289 value_field->setDisabled(
true);
1292 if (options.has_max_length())
1294 if (field_desc->type() == google::protobuf::FieldDescriptor::TYPE_STRING)
1296 WLengthValidator* validator =
new WLengthValidator(0, options.max_length());
1297 value_field->setValidator(validator);
1299 else if (field_desc->type() == google::protobuf::FieldDescriptor::TYPE_BYTES)
1301 WRegExpValidator* validator =
1302 new WRegExpValidator(
"([0-9,a-f,A-F][0-9,a-f,A-F]){0," +
1303 goby::util::as<std::string>(options.max_length()) +
"}");
1305 value_field->setValidator(validator);
1309 if (options.codec() ==
"_time")
1311 value_field->setDisabled(
true);
1312 set_time_field(value_field, field_desc);
1313 time_fields_.insert(std::make_pair(value_field, field_desc));
1317 void goby::common::LiaisonCommander::ControlsContainer::CommandContainer::dccl_default_modify_field(
1318 WFormWidget*& modify_field,
const google::protobuf::FieldDescriptor* field_desc)
1320 const dccl::DCCLFieldOptions& options = field_desc->options().GetExtension(dccl::field);
1322 if (options.has_max_repeat())
1324 if (WSpinBox* spin_box = dynamic_cast<WSpinBox*>(modify_field))
1325 spin_box->setMaximum(options.max_repeat());
1330 goby::common::LiaisonCommander::ControlsContainer::CommandContainer::string_from_dccl_double(
1331 double* value,
const google::protobuf::FieldDescriptor* field_desc)
1333 const dccl::DCCLFieldOptions& options = field_desc->options().GetExtension(dccl::field);
1334 *value = goby::util::unbiased_round(*value, options.precision());
1336 if (options.precision() < 0)
1338 return goby::util::as<std::string>(
1339 *value, std::max(0.0, std::log10(std::abs(*value)) + options.precision()),
1340 goby::util::FLOAT_SCIENTIFIC);
1344 return goby::util::as<std::string>(*value, options.precision(), goby::util::FLOAT_FIXED);
1353 void goby::common::LiaisonCommander::ControlsContainer::CommandContainer::
1355 const google::protobuf::FieldDescriptor* field_desc,
1356 WTreeTableNode* parent)
1358 const google::protobuf::Reflection* refl = message->GetReflection();
1361 while (desired_size > static_cast<int>(parent->childNodes().size()))
1363 int index = parent->childNodes().size();
1364 WTreeTableNode* node =
1365 new WTreeTableNode(
"index: " + goby::util::as<std::string>(index), 0, parent);
1367 if ((parent->styleClass() == STRIPE_ODD_CLASS && index % 2) ||
1368 (parent->styleClass() == STRIPE_EVEN_CLASS && !(index % 2)))
1369 node->setStyleClass(STRIPE_ODD_CLASS);
1371 node->setStyleClass(STRIPE_EVEN_CLASS);
1373 WFormWidget* value_field = 0;
1375 if (field_desc->cpp_type() == google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE)
1377 if (refl->FieldSize(*message, field_desc) <= index)
1379 generate_tree(node, refl->AddMessage(message, field_desc));
1384 generate_tree(node, refl->MutableRepeatedMessage(message, field_desc, index));
1389 generate_tree_field(value_field, message, field_desc, index);
1393 node->setColumnWidget(1, value_field);
1399 while (desired_size < static_cast<int>(parent->childNodes().size()))
1401 parent->removeChildNode(parent->childNodes().back());
1403 refl->RemoveLast(message, field_desc);
1407 void goby::common::LiaisonCommander::ControlsContainer::CommandContainer::
1409 const google::protobuf::FieldDescriptor* field_desc,
1410 WPushButton* button, WTreeTableNode* parent)
1412 if (button->text() == MESSAGE_INCLUDE_TEXT)
1414 generate_tree(parent, message->GetReflection()->MutableMessage(message, field_desc));
1418 button->setText(MESSAGE_REMOVE_TEXT);
1422 const std::vector<WTreeNode*> children = parent->childNodes();
1423 message->GetReflection()->ClearField(message, field_desc);
1424 for (
int i = 0, n = children.size(); i < n; ++i) parent->removeChildNode(children[i]);
1426 button->setText(MESSAGE_INCLUDE_TEXT);
google::protobuf::uint32 uint32
an unsigned 32 bit integer
void parse_for_moos(const std::string &in, google::protobuf::Message *msg)
Parses the string in to Google Protocol Buffers message msg. All errors are written to the goby::util...
Helpers for MOOS applications for serializing and parsed Google Protocol buffers messages.
ReturnType goby_time()
Returns current UTC time as a boost::posix_time::ptime.
google::protobuf::int64 int64
a signed 64 bit integer
common::FlexOstream glog
Access the Goby logger through this object.
The global namespace for the Goby project.
google::protobuf::uint64 uint64
an unsigned 64 bit integer
boost::posix_time::ptime unix_double2ptime(double given_time)
convert to boost date_time ptime from the number of seconds (including fractional) since 1/1/1970 0:0...
google::protobuf::int32 int32
a signed 32 bit integer