Goby v2
liaison_commander.cpp
1 // Copyright 2009-2018 Toby Schneider (http://gobysoft.org/index.wt/people/toby)
2 // GobySoft, LLC (2013-)
3 // Massachusetts Institute of Technology (2007-2014)
4 // Community contributors (see AUTHORS file)
5 //
6 //
7 // This file is part of the Goby Underwater Autonomy Project Libraries
8 // ("The Goby Libraries").
9 //
10 // The Goby Libraries are free software: you can redistribute them and/or modify
11 // them under the terms of the GNU Lesser General Public License as published by
12 // the Free Software Foundation, either version 2.1 of the License, or
13 // (at your option) any later version.
14 //
15 // The Goby Libraries are distributed in the hope that they will be useful,
16 // but WITHOUT ANY WARRANTY; without even the implied warranty of
17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 // GNU Lesser General Public License for more details.
19 //
20 // You should have received a copy of the GNU Lesser General Public License
21 // along with Goby. If not, see <http://www.gnu.org/licenses/>.
22 
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>
29 #include <Wt/WPanel>
30 #include <Wt/WRegExpValidator>
31 #include <cfloat>
32 #include <cmath>
33 
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"
39 
40 #include "liaison_commander.h"
41 
42 using namespace Wt;
43 using namespace goby::common::logger;
44 using goby::glog;
45 using goby::common::logger_lock::lock;
46 using goby::moos::operator<<;
47 
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
52  goby::common::LiaisonCommander::last_db_update_time_(goby::common::goby_time());
53 
54 const std::string MESSAGE_INCLUDE_TEXT = "include";
55 const std::string MESSAGE_REMOVE_TEXT = "remove";
56 
57 const std::string STRIPE_ODD_CLASS = "odd";
58 const std::string STRIPE_EVEN_CLASS = "even";
59 
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)),
65  // main_layout_(new WVBoxLayout(this)),
66  commands_div_(new WStackedWidget),
67  controls_div_(new ControlsContainer(this, pb_commander_config_, commands_div_, this))
68 {
69  addWidget(commands_div_);
70 
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());
78 
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());
85 
86  zeromq_service_->merge_cfg(ipc_sockets);
87 
88  // main_layout_->addWidget(controls_div_);
89  // main_layout_->addWidget(commands_div_);
90  // main_layout_->addStretch(1);
91 
92  goby::moos::moos_technique = pb_commander_config_.moos_parser_technique();
93 
94  for (int i = 0, n = pb_commander_config_.subscription_size(); i < n; ++i)
95  {
96  display_subscriptions_.insert(pb_commander_config_.subscription(i));
97  subscribe(pb_commander_config_.subscription(i), LIAISON_INTERNAL_SUBSCRIBE_SOCKET);
98  }
99 
100  if (pb_commander_config_.has_time_source_var())
101  subscribe(pb_commander_config_.time_source_var(), LIAISON_INTERNAL_SUBSCRIBE_SOCKET);
102 
103  subscribe(pb_commander_config_.network_ack_var(), LIAISON_INTERNAL_SUBSCRIBE_SOCKET);
104 
105  commander_timer_.setInterval(1 / cfg.update_freq() * 1.0e3);
106  commander_timer_.timeout().connect(this, &LiaisonCommander::loop);
107 
108  set_name("MOOSCommander");
109 }
110 
111 void goby::common::LiaisonCommander::moos_inbox(CMOOSMsg& msg)
112 {
113  glog.is(DEBUG1) && glog << "LiaisonCommander: Got message: " << msg << std::endl;
114 
115  if (msg.GetKey() == pb_commander_config_.network_ack_var())
116  {
117  std::string value = msg.GetAsString();
119  parse_for_moos(value, &ack);
120 
121  Dbo::ptr<CommandEntry> acked_command(static_cast<goby::common::CommandEntry*>(0));
122  {
123  boost::mutex::scoped_lock slock(dbo_mutex_);
124  Dbo::Transaction transaction(controls_div_->session_);
125  acked_command = controls_div_->session_.find<CommandEntry>()
126  .where("utime = ?")
127  .bind((long long)ack.message_time());
128  if (acked_command)
129  {
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());
135 
136  if (ack.ack_type() == goby::acomms::protobuf::NetworkAck::ACK)
137  acked_command.modify()->last_ack = ack.ack_src();
138 
139  bool seen_ack = false;
140  for (int i = 0, n = ack_set.ack_size(); i < n; ++i)
141  {
142  if (ack_set.ack(i).ack_src() == ack.ack_src())
143  seen_ack = true;
144  }
145  if (!seen_ack)
146  ack_set.add_ack()->CopyFrom(ack);
147 
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();
152  last_db_update_time_ = goby::common::goby_time();
153  }
154  }
155  }
156 
157  if (display_subscriptions_.count(msg.GetKey()))
158  {
159  WContainerWidget* new_div = new WContainerWidget(controls_div_->incoming_message_stack_);
160 
161  new WText("Message: " + goby::util::as<std::string>(
162  controls_div_->incoming_message_stack_->children().size()),
163  new_div);
164 
165  WGroupBox* box =
166  new WGroupBox(msg.GetKey() + " @ " +
167  boost::posix_time::to_simple_string(
168  goby::util::as<boost::posix_time::ptime>(msg.GetTime())),
169  new_div);
170 
171  std::string value = msg.GetAsString();
172 
173  boost::shared_ptr<google::protobuf::Message> pb_msg = dynamic_parse_for_moos(value);
174 
175  if (pb_msg)
176  new WText("<pre>" + pb_msg->DebugString() + "</pre>", box);
177  else
178  new WText(value, PlainText, box);
179 
180  WPushButton* minus = new WPushButton("-", new_div);
181  WPushButton* plus = new WPushButton("+", new_div);
182 
183  WPushButton* remove = new WPushButton("x", new_div);
184  remove->setFloatSide(Wt::Right);
185 
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);
189 
190  controls_div_->incoming_message_stack_->setCurrentIndex(
191  controls_div_->incoming_message_stack_->children().size() - 1);
192  }
193 }
194 
195 void goby::common::LiaisonCommander::ControlsContainer::increment_incoming_messages(
196  const WMouseEvent& event)
197 {
198  int new_index = incoming_message_stack_->currentIndex() + 1;
199  if (new_index == static_cast<int>(incoming_message_stack_->children().size()))
200  new_index = 0;
201 
202  incoming_message_stack_->setCurrentIndex(new_index);
203 }
204 
205 void goby::common::LiaisonCommander::ControlsContainer::decrement_incoming_messages(
206  const WMouseEvent& event)
207 {
208  int new_index = static_cast<int>(incoming_message_stack_->currentIndex()) - 1;
209  if (new_index < 0)
210  new_index = incoming_message_stack_->children().size() - 1;
211 
212  incoming_message_stack_->setCurrentIndex(new_index);
213 }
214 
215 void goby::common::LiaisonCommander::ControlsContainer::remove_incoming_message(
216  const WMouseEvent& event)
217 {
218  WWidget* remove = incoming_message_stack_->currentWidget();
219  decrement_incoming_messages(event);
220  incoming_message_stack_->removeWidget(remove);
221 }
222 
223 void goby::common::LiaisonCommander::loop()
224 {
225  ControlsContainer::CommandContainer* current_command =
226  dynamic_cast<ControlsContainer::CommandContainer*>(
227  controls_div_->commands_div_->currentWidget());
228 
229  while (zeromq_service_->poll(0)) {}
230 
231  if (current_command && current_command->time_fields_.size())
232  {
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();
236  it != end; ++it)
237  current_command->set_time_field(it->first, it->second);
238  }
239 
240  if (current_command && (last_db_update_time_ > current_command->last_reload_time_))
241  {
242  glog.is(DEBUG1) && glog << "Reloading command!" << std::endl;
243  glog.is(DEBUG1) && glog << last_db_update_time_ << "/" << current_command->last_reload_time_
244  << std::endl;
245 
246  boost::mutex::scoped_lock slock(dbo_mutex_);
247  Dbo::Transaction transaction(controls_div_->session_);
248  current_command->query_model_->reload();
249  current_command->last_reload_time_ = goby::common::goby_time();
250  }
251 }
252 
253 goby::common::LiaisonCommander::ControlsContainer::ControlsContainer(
254  MOOSNode* moos_node, const protobuf::ProtobufCommanderConfig& pb_commander_config,
255  WStackedWidget* commands_div, WContainerWidget* parent /*=0*/)
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),
263  // incoming_message_panel_(new Wt::WPanel(this)),
264  incoming_message_stack_(new Wt::WStackedWidget(this))
265 // master_field_info_panel_(new Wt::WPanel(this)),
266 // master_field_info_stack_(new Wt::WStackedWidget(this))
267 {
268  // if we're the first thread, make the database connection
269  if (!sqlite3_)
270  {
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()));
275  }
276 
277  {
278  boost::mutex::scoped_lock slock(dbo_mutex_);
279  session_.setConnectionPool(*connection_pool_);
280  session_.mapClass<CommandEntry>("_liaison_commands");
281 
282  try
283  {
284  session_.createTables();
285  }
286  catch (Dbo::Exception& e)
287  {
288  glog.is(VERBOSE) && glog << "Could not create tables: " << e.what() << std::endl;
289  }
290  }
291 
292  // incoming_message_panel_->setPositionScheme(Wt::Fixed);
293  // incoming_message_panel_->setOffsets(20, Wt::Left | Wt::Bottom);
294 
295  // incoming_message_panel_->setTitle("Incoming messages");
296  // incoming_message_panel_->setCollapsible(true);
297  // incoming_message_panel_->setCentralWidget(incoming_message_stack_);
298  // incoming_message_panel_->addStyleClass("fixed-left");
299  incoming_message_stack_->addStyleClass("fixed-left");
300 
301  // Wt::WCssDecorationStyle field_info_style;
302  // field_info_style.setBackgroundColor(WColor(white));
303 
304  // master_field_info_panel_->setTitle("Field Information");
305  // master_field_info_panel_->setCollapsible(true);
306  // master_field_info_panel_->setCentralWidget(master_field_info_stack_);
307 
308  // master_field_info_panel_->setDecorationStyle(field_info_style);
309 
310  // master_field_info_panel_->setPositionScheme(Wt::Fixed);
311  // master_field_info_panel_->setOffsets(20, Wt::Right | Wt::Bottom);
312  // master_field_info_panel_->setStyleClass("fixed-right");
313  // master_field_info_stack_->setStyleClass("fixed-right");
314 
315  send_button_->setDisabled(true);
316  clear_button_->setDisabled(true);
317  comment_line_->setDisabled(true);
318 
319  comment_label_->setBuddy(comment_line_);
320 
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);
324 
325  Dbo::ptr<CommandEntry> last_command(static_cast<goby::common::CommandEntry*>(0));
326  {
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");
330  if (last_command)
331  glog.is(DEBUG1) && glog << "Last command was of type: " << last_command->protobuf_name
332  << std::endl;
333  }
334 
335  command_selection_->activated().connect(this, &ControlsContainer::switch_command);
336 
337  for (int i = 0, n = pb_commander_config.load_protobuf_name_size(); i < n; ++i)
338  {
339  const google::protobuf::Descriptor* desc =
340  goby::util::DynamicProtobufManager::find_descriptor(
341  pb_commander_config.load_protobuf_name(i));
342 
343  if (!desc)
344  {
345  glog.is(WARN) &&
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`)"
348  << std::endl;
349  }
350  else
351  {
352  command_selection_->addItem(pb_commander_config.load_protobuf_name(i));
353  }
354  }
355 
356  command_selection_->model()->sort(0);
357 
358  if (last_command)
359  {
360  int last_command_index = command_selection_->findText(last_command->protobuf_name);
361  if (last_command_index >= 0)
362  {
363  command_selection_->setCurrentIndex(last_command_index);
364  switch_command(command_selection_->currentIndex());
365  }
366  }
367 }
368 
369 void goby::common::LiaisonCommander::ControlsContainer::switch_command(int selection_index)
370 {
371  if (selection_index == 0)
372  {
373  send_button_->setDisabled(true);
374  clear_button_->setDisabled(true);
375  comment_line_->setDisabled(true);
376  return;
377  }
378 
379  send_button_->setDisabled(false);
380  clear_button_->setDisabled(false);
381  comment_line_->setDisabled(false);
382 
383  std::string protobuf_name = command_selection_->itemText(selection_index).narrow();
384 
385  if (!commands_.count(protobuf_name))
386  {
387  CommandContainer* new_command =
388  new CommandContainer(moos_node_, pb_commander_config_, protobuf_name, &session_);
389 
390  //master_field_info_stack_);
391  commands_div_->addWidget(new_command);
392  // index of the newly added widget
393  commands_[protobuf_name] = commands_div_->count() - 1;
394  }
395  commands_div_->setCurrentIndex(commands_[protobuf_name]);
396  // master_field_info_stack_->setCurrentIndex(commands_[protobuf_name]);
397 }
398 
399 void goby::common::LiaisonCommander::ControlsContainer::clear_message()
400 {
401  WDialog dialog("Confirm clearing of message: " + command_selection_->currentText());
402  WPushButton ok("Clear", dialog.contents());
403  WPushButton cancel("Cancel", dialog.contents());
404 
405  dialog.rejectWhenEscapePressed();
406  ok.clicked().connect(&dialog, &WDialog::accept);
407  cancel.clicked().connect(&dialog, &WDialog::reject);
408 
409  if (dialog.exec() == WDialog::Accepted)
410  {
411  CommandContainer* current_command =
412  dynamic_cast<CommandContainer*>(commands_div_->currentWidget());
413  current_command->message_->Clear();
414  current_command->generate_root();
415  }
416 }
417 
418 void goby::common::LiaisonCommander::ControlsContainer::send_message()
419 {
420  glog.is(VERBOSE) && glog << "Message to be sent!" << std::endl;
421 
422  WDialog dialog("Confirm sending of message: " + command_selection_->currentText());
423 
424  CommandContainer* current_command =
425  dynamic_cast<CommandContainer*>(commands_div_->currentWidget());
426 
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());
430 
431  WGroupBox* message_box = new WGroupBox("Message to send", dialog.contents());
432  WContainerWidget* message_div = new WContainerWidget(message_box);
433 
434  new WText("<pre>" + current_command->message_->DebugString() + "</pre>", message_div);
435 
436  message_div->setMaximumSize(pb_commander_config_.modal_dimensions().width(),
437  pb_commander_config_.modal_dimensions().height());
438  message_div->setOverflow(WContainerWidget::OverflowAuto);
439 
440  // dialog.setResizable(true);
441 
442  WPushButton ok("Send", dialog.contents());
443  WPushButton cancel("Cancel", dialog.contents());
444 
445  dialog.rejectWhenEscapePressed();
446 
447  ok.clicked().connect(&dialog, &WDialog::accept);
448  cancel.clicked().connect(&dialog, &WDialog::reject);
449 
450  if (dialog.exec() == WDialog::Accepted)
451  {
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);
456 
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();
463 
464  boost::posix_time::ptime now = goby::common::goby_time();
465  command_entry->time.setPosixTime(now);
466  command_entry->utime = current_command->latest_time_;
467 
468  command_entry->comment = comment_line->text().narrow();
469  command_entry->last_ack = 0;
470  session_.add(command_entry);
471 
472  {
473  boost::mutex::scoped_lock slock(dbo_mutex_);
474  Dbo::Transaction transaction(*current_command->session_);
475  transaction.commit();
476  last_db_update_time_ = now;
477  }
478 
479  comment_line_->setText("");
480  current_command->query_model_->reload();
481  }
482 }
483 
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)
487  // WStackedWidget* master_field_info_stack)
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_)),
492  // field_info_stack_(new WStackedWidget(master_field_info_stack)),
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)
497 {
498  // new WText("", field_info_stack_);
499  //field_info_map_[0] = 0;
500 
501  tree_table_->addColumn("Value", pb_commander_config.value_width_pixels());
502  tree_table_->addColumn("Modify", pb_commander_config.modify_width_pixels());
503 
504  {
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 + "'"));
509  }
510 
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");
516 
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),
526  WLength::Auto);
527 
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());
532 
533  query_table_->setColumnWidth(protobuf::ProtobufCommanderConfig::COLUMN_IP,
534  pb_commander_config.database_width().ip_width());
535 
536  query_table_->setColumnWidth(protobuf::ProtobufCommanderConfig::COLUMN_TIME,
537  pb_commander_config.database_width().time_width());
538 
539  query_table_->setColumnWidth(protobuf::ProtobufCommanderConfig::COLUMN_LAST_ACK,
540  pb_commander_config.database_width().last_ack_width());
541 
542  query_table_->clicked().connect(this, &CommandContainer::handle_database_double_click);
543 
544  if (query_model_->rowCount() > 0)
545  {
546  const Dbo::ptr<CommandEntry>& entry = query_model_->resultRow(0);
547  message_->ParseFromArray(&entry->bytes[0], entry->bytes.size());
548  }
549 
550  glog.is(DEBUG1) && glog << "Model has " << query_model_->rowCount() << " rows" << std::endl;
551 
552  generate_root();
553 }
554 
555 void goby::common::LiaisonCommander::ControlsContainer::CommandContainer::
556  handle_database_double_click(const WModelIndex& index, const WMouseEvent& event)
557 {
558  glog.is(DEBUG1) && glog << "clicked: " << index.row() << "," << index.column() << std::endl;
559 
560  const Dbo::ptr<CommandEntry>& entry = query_model_->resultRow(index.row());
561 
562  boost::shared_ptr<google::protobuf::Message> message(message_->New());
563  message->ParseFromArray(&entry->bytes[0], entry->bytes.size());
564 
565  if (!message)
566  {
567  glog.is(WARN) && glog << "Invalid message!" << std::endl;
568  return;
569  }
570 
571  database_dialog_.reset(new WDialog("Viewing log entry: " + entry->protobuf_name +
572  " posted at " + entry->time.toString()));
573 
574  WGroupBox* comment_box = new WGroupBox("Log comment", database_dialog_->contents());
575  new WText(entry->comment, comment_box);
576 
577  WContainerWidget* contents_div = new WContainerWidget(database_dialog_->contents());
578  WGroupBox* message_box = new WGroupBox("Message posted", contents_div);
579 
580  WContainerWidget* message_div = new WContainerWidget(message_box);
581 
582  new WText("<pre>" + message->DebugString() + "</pre>", message_div);
583 
584  protobuf::NetworkAckSet acks;
585  acks.ParseFromArray(&entry->acks[0], entry->acks.size());
586 
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);
590 
591  contents_div->setMaximumSize(pb_commander_config_.modal_dimensions().width(),
592  pb_commander_config_.modal_dimensions().height());
593  contents_div->setOverflow(WContainerWidget::OverflowAuto);
594 
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());
598 
599  database_dialog_->rejectWhenEscapePressed();
600 
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));
607 
608  database_dialog_->show();
609  // merge.clicked().connect(&dialog, &WDialog::accept);
610  // cancel.clicked().connect(&dialog, &WDialog::reject);
611 }
612 
613 void goby::common::LiaisonCommander::ControlsContainer::CommandContainer::handle_database_dialog(
614  DatabaseDialogResponse response, boost::shared_ptr<google::protobuf::Message> message)
615 {
616  switch (response)
617  {
618  case RESPONSE_EDIT:
619  message_->CopyFrom(*message);
620  generate_root();
621  database_dialog_->accept();
622  break;
623 
624  case RESPONSE_MERGE:
625  message->MergeFrom(*message_);
626  message_->CopyFrom(*message);
627  generate_root();
628  database_dialog_->accept();
629  break;
630 
631  case RESPONSE_CANCEL: database_dialog_->reject(); break;
632  }
633 }
634 
635 void goby::common::LiaisonCommander::ControlsContainer::CommandContainer::generate_root()
636 {
637  const google::protobuf::Descriptor* desc = message_->GetDescriptor();
638 
639  // Create and set the root node
640  WTreeTableNode* root = new WTreeTableNode(desc->name());
641  root->setImagePack("resources/");
642  root->setStyleClass(STRIPE_EVEN_CLASS);
643 
644  // deletes an existing root
645  tree_table_->setTreeRoot(root, "Field");
646 
647  time_fields_.clear();
648 
649  generate_tree(root, message_.get());
650 
651  root->expand();
652 }
653 
654 void goby::common::LiaisonCommander::ControlsContainer::CommandContainer::generate_tree(
655  WTreeTableNode* parent, google::protobuf::Message* message)
656 {
657  const google::protobuf::Descriptor* desc = message->GetDescriptor();
658 
659  for (int i = 0, n = desc->field_count(); i < n; ++i)
660  generate_tree_row(parent, message, desc->field(i));
661 
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]);
667 }
668 
669 void goby::common::LiaisonCommander::ControlsContainer::CommandContainer::generate_tree_row(
670  WTreeTableNode* parent, google::protobuf::Message* message,
671  const google::protobuf::FieldDescriptor* field_desc)
672 {
673  const google::protobuf::Reflection* refl = message->GetReflection();
674 
675  if (field_desc->options().GetExtension(dccl::field).omit())
676  return;
677 
678  int index = parent->childNodes().size();
679 
680  LiaisonTreeTableNode* node =
681  new LiaisonTreeTableNode(field_desc->is_extension() ? "[" + field_desc->full_name() + "]: "
682  : field_desc->name() + ": ",
683  0, parent);
684 
685  if ((parent->styleClass() == STRIPE_ODD_CLASS && index % 2) ||
686  (parent->styleClass() == STRIPE_EVEN_CLASS && !(index % 2)))
687  node->setStyleClass(STRIPE_ODD_CLASS);
688  else
689  node->setStyleClass(STRIPE_EVEN_CLASS);
690 
691  WFormWidget* value_field = 0;
692  WFormWidget* modify_field = 0;
693  if (field_desc->is_repeated())
694  {
695  // WContainerWidget* div = new WContainerWidget;
696  // WLabel* label = new WLabel(": ", div);
697  WSpinBox* spin_box = new WSpinBox;
698  spin_box->setTextSize(3);
699  // label->setBuddy(spin_box);
700  spin_box->setRange(0, std::numeric_limits<int>::max());
701  spin_box->setSingleStep(1);
702 
703  spin_box->valueChanged().connect(boost::bind(&CommandContainer::handle_repeated_size_change,
704  this, _1, message, field_desc, node));
705 
706  spin_box->setValue(refl->FieldSize(*message, field_desc));
707  spin_box->valueChanged().emit(refl->FieldSize(*message, field_desc));
708 
709  modify_field = spin_box;
710  }
711  else
712  {
713  if (field_desc->cpp_type() == google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE)
714  {
715  if (field_desc->is_required())
716  {
717  generate_tree(node, message->GetReflection()->MutableMessage(message, field_desc));
718  node->expand();
719  }
720  else
721  {
722  WPushButton* button = new WPushButton(MESSAGE_INCLUDE_TEXT);
723 
724  button->clicked().connect(
725  boost::bind(&CommandContainer::handle_toggle_single_message, this, _1, message,
726  field_desc, button, node));
727 
728  if (refl->HasField(*message, field_desc))
729  {
730  parent->expand();
731  handle_toggle_single_message(WMouseEvent(), message, field_desc, button, node);
732  }
733 
734  modify_field = button;
735  }
736  }
737  else
738  {
739  generate_tree_field(value_field, message, field_desc);
740  }
741  }
742  if (value_field)
743  node->setColumnWidget(1, value_field);
744 
745  if (modify_field)
746  {
747  dccl_default_modify_field(modify_field, field_desc);
748 
749  generate_field_info_box(modify_field, field_desc);
750 
751  node->setColumnWidget(2, modify_field);
752  }
753 }
754 
755 void goby::common::LiaisonCommander::ControlsContainer::CommandContainer::generate_tree_field(
756  WFormWidget*& value_field, google::protobuf::Message* message,
757  const google::protobuf::FieldDescriptor* field_desc, int index /*= -1*/)
758 {
759  const google::protobuf::Reflection* refl = message->GetReflection();
760 
761  switch (field_desc->cpp_type())
762  {
763  case google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE: break;
764 
765  case google::protobuf::FieldDescriptor::CPPTYPE_INT32:
766  {
767  WIntValidator* validator = new WIntValidator;
768 
769  if (field_desc->is_repeated() && refl->FieldSize(*message, field_desc) <= index)
770  refl->AddInt32(message, field_desc, field_desc->default_value_int32());
771 
772  int32 value = field_desc->is_repeated()
773  ? refl->GetRepeatedInt32(*message, field_desc, index)
774  : refl->GetInt32(*message, field_desc);
775 
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);
779  }
780  break;
781 
782  case google::protobuf::FieldDescriptor::CPPTYPE_INT64:
783  {
784  WIntValidator* validator = 0;
785 
786  if (field_desc->is_repeated() && refl->FieldSize(*message, field_desc) <= index)
787  refl->AddInt64(message, field_desc, field_desc->default_value_int64());
788 
789  int64 value = field_desc->is_repeated()
790  ? refl->GetRepeatedInt64(*message, field_desc, index)
791  : refl->GetInt64(*message, field_desc);
792 
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);
796  }
797  break;
798 
799  case google::protobuf::FieldDescriptor::CPPTYPE_UINT32:
800  {
801  WIntValidator* validator = new WIntValidator;
802  validator->setBottom(0);
803 
804  if (field_desc->is_repeated() && refl->FieldSize(*message, field_desc) <= index)
805  refl->AddUInt32(message, field_desc, field_desc->default_value_uint32());
806 
807  uint32 value = field_desc->is_repeated()
808  ? refl->GetRepeatedUInt32(*message, field_desc, index)
809  : refl->GetUInt32(*message, field_desc);
810 
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);
814  }
815  break;
816 
817  case google::protobuf::FieldDescriptor::CPPTYPE_UINT64:
818  {
819  WIntValidator* validator = 0;
820 
821  if (field_desc->is_repeated() && refl->FieldSize(*message, field_desc) <= index)
822  refl->AddUInt64(message, field_desc, field_desc->default_value_uint64());
823 
824  uint64 value = field_desc->is_repeated()
825  ? refl->GetRepeatedUInt64(*message, field_desc, index)
826  : refl->GetUInt64(*message, field_desc);
827 
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);
831  }
832  break;
833 
834  case google::protobuf::FieldDescriptor::CPPTYPE_STRING:
835  {
836  if (field_desc->is_repeated() && refl->FieldSize(*message, field_desc) <= index)
837  refl->AddString(message, field_desc, field_desc->default_value_string());
838 
839  WValidator* validator = 0;
840 
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();
845 
846  if (field_desc->type() == google::protobuf::FieldDescriptor::TYPE_BYTES)
847  {
848  validator = new WRegExpValidator("([0-9,a-f,A-F][0-9,a-f,A-F])*");
849 
850  current_str = goby::util::hex_encode(current_str);
851  default_str = goby::util::hex_encode(default_str);
852  }
853  else
854  {
855  validator = new WValidator;
856  }
857 
858  value_field = generate_single_line_edit_field(message, field_desc, current_str,
859  default_str, validator, index);
860  }
861  break;
862 
863  case google::protobuf::FieldDescriptor::CPPTYPE_FLOAT:
864  {
865  if (field_desc->is_repeated() && refl->FieldSize(*message, field_desc) <= index)
866  refl->AddFloat(message, field_desc, field_desc->default_value_float());
867 
868  float value = field_desc->is_repeated()
869  ? refl->GetRepeatedFloat(*message, field_desc, index)
870  : refl->GetFloat(*message, field_desc);
871 
872  WDoubleValidator* validator = new WDoubleValidator;
873  validator->setRange(std::numeric_limits<float>::min(),
874  std::numeric_limits<float>::max());
875 
876  value_field = generate_single_line_edit_field(
877  message, field_desc,
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),
881  validator, index);
882  }
883  break;
884 
885  case google::protobuf::FieldDescriptor::CPPTYPE_DOUBLE:
886  {
887  if (field_desc->is_repeated() && refl->FieldSize(*message, field_desc) <= index)
888  refl->AddDouble(message, field_desc, field_desc->default_value_double());
889 
890  double value = field_desc->is_repeated()
891  ? refl->GetRepeatedDouble(*message, field_desc, index)
892  : refl->GetDouble(*message, field_desc);
893 
894  WDoubleValidator* validator = new WDoubleValidator;
895 
896  value_field = generate_single_line_edit_field(
897  message, field_desc,
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),
901  validator, index);
902  }
903  break;
904 
905  case google::protobuf::FieldDescriptor::CPPTYPE_BOOL:
906  {
907  if (field_desc->is_repeated() && refl->FieldSize(*message, field_desc) <= index)
908  refl->AddBool(message, field_desc, field_desc->default_value_bool());
909 
910  bool value = field_desc->is_repeated()
911  ? refl->GetRepeatedBool(*message, field_desc, index)
912  : refl->GetBool(*message, field_desc);
913 
914  std::vector<WString> strings;
915  strings.push_back("true");
916  strings.push_back("false");
917 
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);
921  }
922  break;
923 
924  case google::protobuf::FieldDescriptor::CPPTYPE_ENUM:
925  {
926  if (field_desc->is_repeated() && refl->FieldSize(*message, field_desc) <= index)
927  refl->AddEnum(message, field_desc, field_desc->default_value_enum());
928 
929  const google::protobuf::EnumValueDescriptor* value =
930  field_desc->is_repeated() ? refl->GetRepeatedEnum(*message, field_desc, index)
931  : refl->GetEnum(*message, field_desc);
932 
933  std::vector<WString> strings;
934 
935  const google::protobuf::EnumDescriptor* enum_desc = field_desc->enum_type();
936 
937  for (int i = 0, n = enum_desc->value_count(); i < n; ++i)
938  strings.push_back(enum_desc->value(i)->name());
939 
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);
943  }
944  break;
945  }
946 
947  dccl_default_value_field(value_field, field_desc);
948  // queue_default_value_field(value_field, field_desc);
949 
950  generate_field_info_box(value_field, field_desc);
951 }
952 void goby::common::LiaisonCommander::ControlsContainer::CommandContainer::generate_field_info_box(
953  Wt::WFormWidget*& value_field, const google::protobuf::FieldDescriptor* field_desc)
954 {
955  // if(!field_info_map_.count(field_desc))
956  // {
957  // WGroupBox* box = new WGroupBox(field_desc->full_name(), field_info_stack_);
958 
959  std::string info;
960 
961  // new WText("[Field] " + field_desc->DebugString(), box);
962 
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)
967  {
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))
971  {
972  std::string ext_str;
973  google::protobuf::TextFormat::PrintFieldValueToString(field_desc->options(),
974  ext_field_desc, -1, &ext_str);
975 
976  if (!info.empty())
977  info += "<br/>";
978 
979  info += "[Options] " + ext_field_desc->full_name() + ": " + ext_str;
980 
981  //new WText("<br/> [Options] " + ext_field_desc->full_name() + ": " + ext_str, box);
982  }
983  }
984 
985  // field_info_map_.insert(std::make_pair(field_desc, field_info_map_.size()));
986  // }
987 
988  //#if WT_VERSION >= 0x03011000
989  // if(!info.empty())
990  // value_field->setToolTip(info, Wt::XHTMLText);
991  //#else
992  // if(!info.empty())
993  // value_field->setToolTip(info);
994  //#endif
995 
996  // value_field->focussed().connect(boost::bind(&CommandContainer::handle_field_focus, this,
997  // field_info_map_[field_desc]));
998  // value_field->blurred().connect(boost::bind(&CommandContainer::handle_field_focus, this, 0));
999 }
1000 
1001 void goby::common::LiaisonCommander::ControlsContainer::CommandContainer::handle_line_field_changed(
1002  google::protobuf::Message* message, const google::protobuf::FieldDescriptor* field_desc,
1003  WLineEdit* field, int index)
1004 {
1005  std::string value = field->text().narrow();
1006 
1007  const google::protobuf::Reflection* refl = message->GetReflection();
1008 
1009  if (value.empty() && field_desc->is_repeated())
1010  value = field->emptyText().narrow();
1011 
1012  if (value.empty() && !field_desc->is_repeated())
1013  {
1014  refl->ClearField(message, field_desc);
1015  }
1016  else
1017  {
1018  switch (field_desc->cpp_type())
1019  {
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));
1025  break;
1026 
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));
1032  break;
1033 
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));
1039  break;
1040 
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));
1046  break;
1047 
1048  case google::protobuf::FieldDescriptor::CPPTYPE_STRING:
1049 
1050  if (field_desc->type() == google::protobuf::FieldDescriptor::TYPE_BYTES)
1051  {
1052  value = goby::util::hex_decode(value);
1053  }
1054 
1055  field_desc->is_repeated()
1056  ? refl->SetRepeatedString(message, field_desc, index, value)
1057  : refl->SetString(message, field_desc, value);
1058  break;
1059 
1060  case google::protobuf::FieldDescriptor::CPPTYPE_FLOAT:
1061  {
1062  double fvalue = goby::util::as<float>(value);
1063 
1064  if (field_desc->options().GetExtension(dccl::field).has_precision())
1065  field->setText(string_from_dccl_double(&fvalue, field_desc));
1066 
1067  field_desc->is_repeated()
1068  ? refl->SetRepeatedFloat(message, field_desc, index, fvalue)
1069  : refl->SetFloat(message, field_desc, fvalue);
1070  }
1071  break;
1072 
1073  case google::protobuf::FieldDescriptor::CPPTYPE_DOUBLE:
1074  {
1075  double dvalue = goby::util::as<double>(value);
1076 
1077  if (field_desc->options().GetExtension(dccl::field).has_precision())
1078  field->setText(string_from_dccl_double(&dvalue, field_desc));
1079 
1080  field_desc->is_repeated()
1081  ? refl->SetRepeatedDouble(message, field_desc, index, dvalue)
1082  : refl->SetDouble(message, field_desc, dvalue);
1083  }
1084  break;
1085 
1086  default: break;
1087  }
1088  }
1089 }
1090 
1091 void goby::common::LiaisonCommander::ControlsContainer::CommandContainer::
1092  handle_combo_field_changed(google::protobuf::Message* message,
1093  const google::protobuf::FieldDescriptor* field_desc,
1094  WComboBox* field, int index)
1095 {
1096  const google::protobuf::Reflection* refl = message->GetReflection();
1097 
1098  if (field->currentIndex() == 0)
1099  refl->ClearField(message, field_desc);
1100  else
1101  {
1102  std::string value = field->currentText().narrow();
1103 
1104  switch (field_desc->cpp_type())
1105  {
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));
1110  break;
1111 
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));
1118  break;
1119 
1120  default: break;
1121  }
1122  }
1123  glog.is(DEBUG1) && glog << "The message is: " << message_->DebugString() << std::endl;
1124 }
1125 
1126 WLineEdit* goby::common::LiaisonCommander::ControlsContainer::CommandContainer::
1127  generate_single_line_edit_field(google::protobuf::Message* message,
1128  const google::protobuf::FieldDescriptor* field_desc,
1129  const std::string& current_value,
1130  const std::string& default_value,
1131  WValidator* validator /* = 0*/, int index /*= -1*/)
1132 {
1133  const google::protobuf::Reflection* refl = message->GetReflection();
1134 
1135  WLineEdit* line_edit = new WLineEdit();
1136 
1137  if (field_desc->has_default_value() || field_desc->is_repeated())
1138  line_edit->setEmptyText(default_value);
1139 
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);
1143 
1144  if (validator)
1145  {
1146  validator->setMandatory(field_desc->is_required());
1147  line_edit->setValidator(validator);
1148  }
1149 
1150  line_edit->changed().connect(boost::bind(&CommandContainer::handle_line_field_changed, this,
1151  message, field_desc, line_edit, index));
1152 
1153  return line_edit;
1154 }
1155 
1156 WComboBox*
1157 goby::common::LiaisonCommander::ControlsContainer::CommandContainer::generate_combo_box_field(
1158  google::protobuf::Message* message, const google::protobuf::FieldDescriptor* field_desc,
1159  const std::vector<WString>& strings, int current_value, const std::string& default_value,
1160  int index /*= -1*/)
1161 {
1162  const google::protobuf::Reflection* refl = message->GetReflection();
1163 
1164  WComboBox* combo_box = new WComboBox;
1165  WStringListModel* model = new WStringListModel(strings, this);
1166 
1167  if (field_desc->has_default_value())
1168  model->insertString(0, "(default: " + default_value + ")");
1169  else
1170  model->insertString(0, "");
1171 
1172  combo_box->setModel(model);
1173 
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);
1177 
1178  combo_box->changed().connect(boost::bind(&CommandContainer::handle_combo_field_changed, this,
1179  message, field_desc, combo_box, index));
1180 
1181  return combo_box;
1182 }
1183 
1184 // void goby::common::LiaisonCommander::ControlsContainer::CommandContainer::queue_default_value_field(WFormWidget*& value_field,
1185 // const google::protobuf::FieldDescriptor* field_desc)
1186 // {
1187 // const QueueFieldOptions& queue_options = field_desc->options().GetExtension(goby::field).queue();
1188 // if(queue_options.is_time())
1189 // {
1190 // value_field->setDisabled(true);
1191 // set_time_field(value_field, field_desc);
1192 // time_field_ = std::make_pair(value_field, field_desc);
1193 // }
1194 // else if(queue_options.is_src())
1195 // {
1196 // if(WLineEdit* line_edit = dynamic_cast<WLineEdit*>(value_field))
1197 // {
1198 // if(pb_commander_config_.has_modem_id())
1199 // {
1200 // line_edit->setDisabled(true);
1201 // line_edit->setText(goby::util::as<std::string>(pb_commander_config_.modem_id()));
1202 // line_edit->changed().emit();
1203 // }
1204 // }
1205 // }
1206 //}
1207 
1208 void goby::common::LiaisonCommander::ControlsContainer::CommandContainer::set_time_field(
1209  WFormWidget* value_field, const google::protobuf::FieldDescriptor* field_desc)
1210 {
1211  if (WLineEdit* line_edit = dynamic_cast<WLineEdit*>(value_field))
1212  {
1213  boost::posix_time::ptime now;
1214  if (pb_commander_config_.has_time_source_var())
1215  {
1216  CMOOSMsg& newest = moos_node_->newest(pb_commander_config_.time_source_var());
1217  now = newest.IsDouble() ? unix_double2ptime(newest.GetDouble())
1218  : unix_double2ptime(goby::util::as<double>(newest.GetString()));
1219  }
1220  else
1221  {
1222  now = goby_time();
1223  }
1224 
1225  const dccl::DCCLFieldOptions& options = field_desc->options().GetExtension(dccl::field);
1226  latest_time_ = goby::util::as<uint64>(now);
1227  enum
1228  {
1229  MICROSEC_ORDER_MAG = 6
1230  };
1231 
1232  switch (field_desc->cpp_type())
1233  {
1234  default: line_edit->setText("Error: invalid goby-acomms time type"); break;
1235 
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);
1241  else
1242  latest_time_ = dccl::round(latest_time_, options.precision());
1243  break;
1244 
1245  case google::protobuf::FieldDescriptor::CPPTYPE_STRING:
1246  line_edit->setText(goby::util::as<std::string>(now));
1247  break;
1248 
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)));
1252 
1253  latest_time_ = dccl::round(latest_time_, options.precision() - MICROSEC_ORDER_MAG);
1254 
1255  break;
1256  }
1257  line_edit->changed().emit();
1258  }
1259 }
1260 
1261 void goby::common::LiaisonCommander::ControlsContainer::CommandContainer::dccl_default_value_field(
1262  WFormWidget*& value_field, const google::protobuf::FieldDescriptor* field_desc)
1263 {
1264  const dccl::DCCLFieldOptions& options = field_desc->options().GetExtension(dccl::field);
1265 
1266  if (options.has_min() && options.has_max())
1267  {
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());
1273  }
1274 
1275  if (options.has_static_value())
1276  {
1277  if (WLineEdit* line_edit = dynamic_cast<WLineEdit*>(value_field))
1278  {
1279  line_edit->setText(options.static_value());
1280  line_edit->changed().emit();
1281  }
1282 
1283  else if (WComboBox* combo_box = dynamic_cast<WComboBox*>(value_field))
1284  {
1285  combo_box->setCurrentIndex(combo_box->findText(options.static_value()));
1286  combo_box->changed().emit();
1287  }
1288 
1289  value_field->setDisabled(true);
1290  }
1291 
1292  if (options.has_max_length())
1293  {
1294  if (field_desc->type() == google::protobuf::FieldDescriptor::TYPE_STRING)
1295  {
1296  WLengthValidator* validator = new WLengthValidator(0, options.max_length());
1297  value_field->setValidator(validator);
1298  }
1299  else if (field_desc->type() == google::protobuf::FieldDescriptor::TYPE_BYTES)
1300  {
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()) + "}");
1304 
1305  value_field->setValidator(validator);
1306  }
1307  }
1308 
1309  if (options.codec() == "_time")
1310  {
1311  value_field->setDisabled(true);
1312  set_time_field(value_field, field_desc);
1313  time_fields_.insert(std::make_pair(value_field, field_desc));
1314  }
1315 }
1316 
1317 void goby::common::LiaisonCommander::ControlsContainer::CommandContainer::dccl_default_modify_field(
1318  WFormWidget*& modify_field, const google::protobuf::FieldDescriptor* field_desc)
1319 {
1320  const dccl::DCCLFieldOptions& options = field_desc->options().GetExtension(dccl::field);
1321 
1322  if (options.has_max_repeat())
1323  {
1324  if (WSpinBox* spin_box = dynamic_cast<WSpinBox*>(modify_field))
1325  spin_box->setMaximum(options.max_repeat());
1326  }
1327 }
1328 
1329 std::string
1330 goby::common::LiaisonCommander::ControlsContainer::CommandContainer::string_from_dccl_double(
1331  double* value, const google::protobuf::FieldDescriptor* field_desc)
1332 {
1333  const dccl::DCCLFieldOptions& options = field_desc->options().GetExtension(dccl::field);
1334  *value = goby::util::unbiased_round(*value, options.precision());
1335 
1336  if (options.precision() < 0)
1337  {
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);
1341  }
1342  else
1343  {
1344  return goby::util::as<std::string>(*value, options.precision(), goby::util::FLOAT_FIXED);
1345  }
1346 }
1347 
1348 // void goby::common::LiaisonCommander::ControlsContainer::CommandContainer::handle_field_focus(int field_info_index)
1349 // {
1350 // field_info_stack_->setCurrentIndex(field_info_index);
1351 // }
1352 
1353 void goby::common::LiaisonCommander::ControlsContainer::CommandContainer::
1354  handle_repeated_size_change(int desired_size, google::protobuf::Message* message,
1355  const google::protobuf::FieldDescriptor* field_desc,
1356  WTreeTableNode* parent)
1357 {
1358  const google::protobuf::Reflection* refl = message->GetReflection();
1359 
1360  // add nodes
1361  while (desired_size > static_cast<int>(parent->childNodes().size()))
1362  {
1363  int index = parent->childNodes().size();
1364  WTreeTableNode* node =
1365  new WTreeTableNode("index: " + goby::util::as<std::string>(index), 0, parent);
1366 
1367  if ((parent->styleClass() == STRIPE_ODD_CLASS && index % 2) ||
1368  (parent->styleClass() == STRIPE_EVEN_CLASS && !(index % 2)))
1369  node->setStyleClass(STRIPE_ODD_CLASS);
1370  else
1371  node->setStyleClass(STRIPE_EVEN_CLASS);
1372 
1373  WFormWidget* value_field = 0;
1374 
1375  if (field_desc->cpp_type() == google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE)
1376  {
1377  if (refl->FieldSize(*message, field_desc) <= index)
1378  {
1379  generate_tree(node, refl->AddMessage(message, field_desc));
1380  }
1381  else
1382  {
1383  parent->expand();
1384  generate_tree(node, refl->MutableRepeatedMessage(message, field_desc, index));
1385  }
1386  }
1387  else
1388  {
1389  generate_tree_field(value_field, message, field_desc, index);
1390  }
1391 
1392  if (value_field)
1393  node->setColumnWidget(1, value_field);
1394  parent->expand();
1395  node->expand();
1396  }
1397 
1398  // remove nodes
1399  while (desired_size < static_cast<int>(parent->childNodes().size()))
1400  {
1401  parent->removeChildNode(parent->childNodes().back());
1402 
1403  refl->RemoveLast(message, field_desc);
1404  }
1405 }
1406 
1407 void goby::common::LiaisonCommander::ControlsContainer::CommandContainer::
1408  handle_toggle_single_message(const WMouseEvent& mouse, google::protobuf::Message* message,
1409  const google::protobuf::FieldDescriptor* field_desc,
1410  WPushButton* button, WTreeTableNode* parent)
1411 {
1412  if (button->text() == MESSAGE_INCLUDE_TEXT)
1413  {
1414  generate_tree(parent, message->GetReflection()->MutableMessage(message, field_desc));
1415 
1416  parent->expand();
1417 
1418  button->setText(MESSAGE_REMOVE_TEXT);
1419  }
1420  else
1421  {
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]);
1425 
1426  button->setText(MESSAGE_INCLUDE_TEXT);
1427  }
1428 }
Definition: time.h:241
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.
Definition: time.h:104
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...
Definition: time.cpp:47
google::protobuf::int32 int32
a signed 32 bit integer