Goby v2
liaison_scope.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 "liaison_scope.h"
24 
25 #include <Wt/Chart/WCartesianChart>
26 #include <Wt/WAnchor>
27 #include <Wt/WApplication>
28 #include <Wt/WDateTime>
29 #include <Wt/WLineEdit>
30 #include <Wt/WPanel>
31 #include <Wt/WPushButton>
32 #include <Wt/WSelectionBox>
33 #include <Wt/WSortFilterProxyModel>
34 #include <Wt/WStandardItem>
35 #include <Wt/WStringListModel>
36 #include <Wt/WTable>
37 #include <Wt/WTextArea>
38 #include <Wt/WTimer>
39 #include <Wt/WVBoxLayout>
40 
41 #include <boost/algorithm/string/regex.hpp>
42 #include <boost/regex.hpp>
43 
45 
46 using namespace Wt;
47 using namespace goby::common::logger_lock;
48 using namespace goby::common::logger;
49 
50 goby::common::LiaisonScope::LiaisonScope(ZeroMQService* zeromq_service,
51  const protobuf::LiaisonConfig& cfg,
52  Wt::WContainerWidget* parent)
53  : LiaisonContainer(parent), MOOSNode(zeromq_service), zeromq_service_(zeromq_service),
54  moos_scope_config_(cfg.GetExtension(protobuf::moos_scope_config)),
55  history_model_(new Wt::WStringListModel(this)),
56  model_(new LiaisonScopeMOOSModel(moos_scope_config_, this)),
57  proxy_(new Wt::WSortFilterProxyModel(this)), main_layout_(new Wt::WVBoxLayout(this)),
58  last_scope_state_(UNKNOWN),
59  subscriptions_div_(new SubscriptionsContainer(this, model_, history_model_, msg_map_)),
60  history_header_div_(
61  new HistoryContainer(this, main_layout_, history_model_, moos_scope_config_)),
62  controls_div_(new ControlsContainer(&scope_timer_, cfg.start_paused(), this,
63  subscriptions_div_, history_header_div_)),
64  regex_filter_div_(new RegexFilterContainer(model_, proxy_, moos_scope_config_)),
65  scope_tree_view_(new LiaisonScopeMOOSTreeView(moos_scope_config_)),
66  bottom_fill_(new WContainerWidget)
67 {
68  protobuf::ZeroMQServiceConfig ipc_sockets;
69  protobuf::ZeroMQServiceConfig::Socket* internal_subscribe_socket = ipc_sockets.add_socket();
70  internal_subscribe_socket->set_socket_type(protobuf::ZeroMQServiceConfig::Socket::SUBSCRIBE);
71  internal_subscribe_socket->set_socket_id(LIAISON_INTERNAL_SUBSCRIBE_SOCKET);
72  internal_subscribe_socket->set_transport(protobuf::ZeroMQServiceConfig::Socket::INPROC);
73  internal_subscribe_socket->set_connect_or_bind(protobuf::ZeroMQServiceConfig::Socket::CONNECT);
74  internal_subscribe_socket->set_socket_name(liaison_internal_publish_socket_name());
75 
76  protobuf::ZeroMQServiceConfig::Socket* internal_publish_socket = ipc_sockets.add_socket();
77  internal_publish_socket->set_socket_type(protobuf::ZeroMQServiceConfig::Socket::PUBLISH);
78  internal_publish_socket->set_socket_id(LIAISON_INTERNAL_PUBLISH_SOCKET);
79  internal_publish_socket->set_transport(protobuf::ZeroMQServiceConfig::Socket::INPROC);
80  internal_publish_socket->set_connect_or_bind(protobuf::ZeroMQServiceConfig::Socket::CONNECT);
81  internal_publish_socket->set_socket_name(liaison_internal_subscribe_socket_name());
82 
83  zeromq_service_->merge_cfg(ipc_sockets);
84 
85  this->resize(WLength::Auto, WLength(100, WLength::Percentage));
86 
87  zeromq_service_->socket_from_id(LIAISON_INTERNAL_SUBSCRIBE_SOCKET)
88  .set_global_blackout(boost::posix_time::milliseconds(1 / cfg.update_freq() * 1e3));
89 
90  setStyleClass("scope");
91 
92  proxy_->setSourceModel(model_);
93  scope_tree_view_->setModel(proxy_);
94  scope_tree_view_->sortByColumn(moos_scope_config_.sort_by_column(),
95  moos_scope_config_.sort_ascending() ? AscendingOrder
96  : DescendingOrder);
97 
98  main_layout_->addWidget(controls_div_);
99  main_layout_->addWidget(subscriptions_div_);
100  main_layout_->addWidget(history_header_div_);
101  main_layout_->addWidget(regex_filter_div_);
102  main_layout_->addWidget(scope_tree_view_);
103  main_layout_->setResizable(main_layout_->count() - 1);
104  main_layout_->addWidget(bottom_fill_, -1, AlignTop);
105  main_layout_->addStretch(1);
106  bottom_fill_->resize(WLength::Auto, 100);
107 
108  for (int i = 0, n = moos_scope_config_.subscription_size(); i < n; ++i)
109  subscriptions_div_->add_subscription(moos_scope_config_.subscription(i));
110 
111  for (int i = 0, n = moos_scope_config_.history_size(); i < n; ++i)
112  history_header_div_->add_history(moos_scope_config_.history(i));
113 
114  goby::moos::moos_technique = moos_scope_config_.moos_parser_technique();
115 
116  wApp->globalKeyPressed().connect(this, &LiaisonScope::handle_global_key);
117 
118  scope_timer_.setInterval(1 / cfg.update_freq() * 1.0e3);
119  scope_timer_.timeout().connect(this, &LiaisonScope::loop);
120 
121  set_name("MOOSScope");
122 }
123 
124 void goby::common::LiaisonScope::loop()
125 {
126  if (!is_paused())
127  {
128  glog.is(DEBUG2) && glog << "LiaisonScope: polling" << std::endl;
129  while (zeromq_service_->poll(0)) {}
130  }
131 }
132 
133 void goby::common::LiaisonScope::attach_pb_rows(const std::vector<Wt::WStandardItem*>& items,
134  CMOOSMsg& msg)
135 {
136  const std::string& value = msg.GetString();
137  boost::shared_ptr<google::protobuf::Message> pb_msg = dynamic_parse_for_moos(value);
138 
139  Wt::WStandardItem* key_item = items[protobuf::MOOSScopeConfig::COLUMN_KEY];
140 
141  std::vector<std::string> result;
142  if (pb_msg)
143  {
144  std::string debug_string = pb_msg->DebugString();
145  boost::trim(debug_string);
146 
147  boost::split(result, debug_string, boost::is_any_of("\n"));
148  items[protobuf::MOOSScopeConfig::COLUMN_TYPE]->setText(
149  pb_msg->GetDescriptor()->full_name() + " (Protobuf)");
150  items[protobuf::MOOSScopeConfig::COLUMN_VALUE]->setText(pb_msg->ShortDebugString());
151  }
152  else
153  {
154  boost::split(result, value, boost::is_any_of(","));
155  }
156 
157  key_item->setRowCount(result.size());
158  key_item->setColumnCount(protobuf::MOOSScopeConfig::COLUMN_MAX + 1);
159 
160  for (int i = 0, n = result.size(); i < n; ++i)
161  {
162  for (int j = 0; j <= protobuf::MOOSScopeConfig::COLUMN_MAX; ++j)
163  {
164  if (!key_item->child(i, j))
165  key_item->setChild(i, j, new Wt::WStandardItem);
166 
167  if (j == protobuf::MOOSScopeConfig::COLUMN_VALUE)
168  {
169  key_item->child(i, j)->setText(result[i]);
170  }
171  else
172  {
173  // so we can still sort by these fields
174  key_item->child(i, j)->setText(items[j]->text());
175  key_item->child(i, j)->setStyleClass("invisible");
176  }
177  }
178  }
179 }
180 
181 std::vector<Wt::WStandardItem*> goby::common::LiaisonScope::create_row(CMOOSMsg& msg)
182 {
183  std::vector<Wt::WStandardItem*> items;
184  for (int i = 0; i <= protobuf::MOOSScopeConfig::COLUMN_MAX; ++i)
185  items.push_back(new WStandardItem);
186  update_row(msg, items);
187 
188  return items;
189 }
190 
191 void goby::common::LiaisonScope::update_row(CMOOSMsg& msg, const std::vector<WStandardItem*>& items)
192 {
193  items[protobuf::MOOSScopeConfig::COLUMN_KEY]->setText(msg.GetKey());
194 
195  items[protobuf::MOOSScopeConfig::COLUMN_TYPE]->setText((msg.IsDouble() ? "double" : "string"));
196 
197  if (msg.IsDouble())
198  items[protobuf::MOOSScopeConfig::COLUMN_VALUE]->setData(msg.GetDouble(), DisplayRole);
199  else
200  items[protobuf::MOOSScopeConfig::COLUMN_VALUE]->setData(msg.GetString(), DisplayRole);
201 
202  items[protobuf::MOOSScopeConfig::COLUMN_TIME]->setData(
203  WDateTime::fromPosixTime(goby::common::unix_double2ptime(msg.GetTime())), DisplayRole);
204 
205  items[protobuf::MOOSScopeConfig::COLUMN_COMMUNITY]->setText(msg.GetCommunity());
206  items[protobuf::MOOSScopeConfig::COLUMN_SOURCE]->setText(msg.m_sSrc);
207  items[protobuf::MOOSScopeConfig::COLUMN_SOURCE_AUX]->setText(msg.GetSourceAux());
208 
209  if (msg.IsString())
210  attach_pb_rows(items, msg);
211 }
212 
213 void goby::common::LiaisonScope::handle_global_key(Wt::WKeyEvent event)
214 {
215  switch (event.key())
216  {
217  // pull single update to display
218  case Key_Enter:
219  subscriptions_div_->refresh_with_newest();
220  history_header_div_->flush_buffer();
221  break;
222 
223  // toggle play/pause
224  case Key_P: controls_div_->handle_play_pause(true);
225 
226  default: break;
227  }
228 }
229 
230 void goby::common::LiaisonScope::pause() { controls_div_->pause(); }
231 
232 void goby::common::LiaisonScope::resume() { controls_div_->resume(); }
233 
234 void goby::common::LiaisonScope::moos_inbox(CMOOSMsg& msg)
235 {
236  if (is_paused())
237  {
238  std::map<std::string, HistoryContainer::MVC>::iterator hist_it =
239  history_header_div_->history_models_.find(msg.GetKey());
240  if (hist_it != history_header_div_->history_models_.end())
241  {
242  // buffer for later display
243  history_header_div_->buffer_.push_back(msg);
244  }
245  }
246  else
247  {
248  handle_message(msg, true);
249  }
250 }
251 
252 void goby::common::LiaisonScope::handle_message(CMOOSMsg& msg, bool fresh_message)
253 {
254  // using goby::moos::operator<<;
255  // glog.is(DEBUG1) && glog << "LiaisonScope: got message: " << msg << std::endl;
256  std::map<std::string, int>::iterator it = msg_map_.find(msg.GetKey());
257  if (it != msg_map_.end())
258  {
259  std::vector<WStandardItem*> items;
260  items.push_back(model_->item(it->second, protobuf::MOOSScopeConfig::COLUMN_KEY));
261  items.push_back(model_->item(it->second, protobuf::MOOSScopeConfig::COLUMN_TYPE));
262  items.push_back(model_->item(it->second, protobuf::MOOSScopeConfig::COLUMN_VALUE));
263  items.push_back(model_->item(it->second, protobuf::MOOSScopeConfig::COLUMN_TIME));
264  items.push_back(model_->item(it->second, protobuf::MOOSScopeConfig::COLUMN_COMMUNITY));
265  items.push_back(model_->item(it->second, protobuf::MOOSScopeConfig::COLUMN_SOURCE));
266  items.push_back(model_->item(it->second, protobuf::MOOSScopeConfig::COLUMN_SOURCE_AUX));
267  update_row(msg, items);
268  }
269  else
270  {
271  std::vector<WStandardItem*> items = create_row(msg);
272  msg_map_.insert(make_pair(msg.GetKey(), model_->rowCount()));
273  model_->appendRow(items);
274  history_model_->addString(msg.GetKey());
275  history_model_->sort(0);
276  regex_filter_div_->handle_set_regex_filter();
277  }
278 
279  if (fresh_message)
280  {
281  history_header_div_->display_message(msg);
282  }
283 }
284 
285 // void goby::common::LiaisonScope::expanded(Wt::WModelIndex index)
286 // {
287 // MOOSNode::set_blackout(boost::any_cast<Wt::WString>(index.data()).narrow(),
288 // boost::posix_time::milliseconds(0));
289 // }
290 
291 // void goby::common::LiaisonScope::collapsed(Wt::WModelIndex index)
292 // {
293 // MOOSNode::clear_blackout(boost::any_cast<Wt::WString>(index.data()).narrow());
294 // }
295 
296 goby::common::LiaisonScopeMOOSTreeView::LiaisonScopeMOOSTreeView(
297  const protobuf::MOOSScopeConfig& moos_scope_config, Wt::WContainerWidget* parent /*= 0*/)
298  : WTreeView(parent)
299 {
300  this->setAlternatingRowColors(true);
301 
302  this->setColumnWidth(protobuf::MOOSScopeConfig::COLUMN_KEY,
303  moos_scope_config.column_width().key_width());
304  this->setColumnWidth(protobuf::MOOSScopeConfig::COLUMN_TYPE,
305  moos_scope_config.column_width().type_width());
306  this->setColumnWidth(protobuf::MOOSScopeConfig::COLUMN_VALUE,
307  moos_scope_config.column_width().value_width());
308  this->setColumnWidth(protobuf::MOOSScopeConfig::COLUMN_TIME,
309  moos_scope_config.column_width().time_width());
310  this->setColumnWidth(protobuf::MOOSScopeConfig::COLUMN_COMMUNITY,
311  moos_scope_config.column_width().community_width());
312  this->setColumnWidth(protobuf::MOOSScopeConfig::COLUMN_SOURCE,
313  moos_scope_config.column_width().source_width());
314  this->setColumnWidth(protobuf::MOOSScopeConfig::COLUMN_SOURCE_AUX,
315  moos_scope_config.column_width().source_aux_width());
316 
317  this->resize(Wt::WLength::Auto, moos_scope_config.scope_height());
318 
319  this->setMinimumSize(moos_scope_config.column_width().key_width() +
320  moos_scope_config.column_width().type_width() +
321  moos_scope_config.column_width().value_width() +
322  moos_scope_config.column_width().time_width() +
323  moos_scope_config.column_width().community_width() +
324  moos_scope_config.column_width().source_width() +
325  moos_scope_config.column_width().source_aux_width() +
326  7 * (protobuf::MOOSScopeConfig::COLUMN_MAX + 1),
327  Wt::WLength::Auto);
328 
329  // this->doubleClicked().connect(this, &LiaisonScopeMOOSTreeView::handle_double_click);
330 }
331 
332 // void goby::common::LiaisonScopeMOOSTreeView::handle_double_click(const Wt::WModelIndex& proxy_index, const Wt::WMouseEvent& event)
333 // {
334 
335 // const Wt::WAbstractProxyModel* proxy = dynamic_cast<const Wt::WAbstractProxyModel*>(this->model());
336 // const Wt::WStandardItemModel* model = dynamic_cast<Wt::WStandardItemModel*>(proxy->sourceModel());
337 // WModelIndex model_index = proxy->mapToSource(proxy_index);
338 
339 // glog.is(DEBUG1) && glog << "clicked: " << model_index.row() << "," << model_index.column() << std::endl;
340 
341 // attach_pb_rows(model->item(model_index.row(), protobuf::MOOSScopeConfig::COLUMN_KEY),
342 // model->item(model_index.row(), protobuf::MOOSScopeConfig::COLUMN_VALUE)->text().narrow());
343 
344 // this->setExpanded(proxy_index, true);
345 // }
346 
347 goby::common::LiaisonScopeMOOSModel::LiaisonScopeMOOSModel(
348  const protobuf::MOOSScopeConfig& moos_scope_config, Wt::WContainerWidget* parent /*= 0*/)
349  : WStandardItemModel(0, protobuf::MOOSScopeConfig::COLUMN_MAX + 1, parent)
350 {
351  this->setHeaderData(protobuf::MOOSScopeConfig::COLUMN_KEY, Horizontal, std::string("Key"));
352  this->setHeaderData(protobuf::MOOSScopeConfig::COLUMN_TYPE, Horizontal, std::string("Type"));
353  this->setHeaderData(protobuf::MOOSScopeConfig::COLUMN_VALUE, Horizontal, std::string("Value"));
354  this->setHeaderData(protobuf::MOOSScopeConfig::COLUMN_TIME, Horizontal, std::string("Time"));
355  this->setHeaderData(protobuf::MOOSScopeConfig::COLUMN_COMMUNITY, Horizontal,
356  std::string("Community"));
357  this->setHeaderData(protobuf::MOOSScopeConfig::COLUMN_SOURCE, Horizontal,
358  std::string("Source"));
359  this->setHeaderData(protobuf::MOOSScopeConfig::COLUMN_SOURCE_AUX, Horizontal,
360  std::string("Source Aux"));
361 }
362 
363 goby::common::LiaisonScope::ControlsContainer::ControlsContainer(
364  Wt::WTimer* timer, bool start_paused, LiaisonScope* scope,
365  SubscriptionsContainer* subscriptions_div, HistoryContainer* history_header_div,
366  Wt::WContainerWidget* parent /*= 0*/)
367  : Wt::WContainerWidget(parent), timer_(timer),
368  play_pause_button_(new WPushButton("Play/Pause [p]", this)),
369  spacer_(new Wt::WText(" ", this)), play_state_(new Wt::WText(this)), is_paused_(start_paused),
370  scope_(scope), subscriptions_div_(subscriptions_div), history_header_div_(history_header_div)
371 {
372  play_pause_button_->clicked().connect(
373  boost::bind(&ControlsContainer::handle_play_pause, this, true));
374 
375  handle_play_pause(false);
376 }
377 
378 goby::common::LiaisonScope::ControlsContainer::~ControlsContainer()
379 {
380  // stop the paused mail thread before destroying
381  is_paused_ = false;
382  if (paused_mail_thread_ && paused_mail_thread_->joinable())
383  paused_mail_thread_->join();
384 }
385 
386 void goby::common::LiaisonScope::ControlsContainer::handle_play_pause(bool toggle_state)
387 {
388  if (toggle_state)
389  is_paused_ = !(is_paused_);
390 
391  is_paused_ ? pause() : resume();
392  play_state_->setText(is_paused_ ? "Paused ([enter] refreshes). " : "Playing... ");
393 }
394 
395 void goby::common::LiaisonScope::ControlsContainer::pause()
396 {
397  // stop the Wt timer and pass control over to a local thread
398  // (so we don't stop reading mail)
399  timer_->stop();
400  is_paused_ = true;
401  paused_mail_thread_.reset(
402  new boost::thread(boost::bind(&ControlsContainer::run_paused_mail, this)));
403 }
404 
405 void goby::common::LiaisonScope::ControlsContainer::resume()
406 {
407  // stop the local thread and pass control over to a Wt
408  is_paused_ = false;
409  if (paused_mail_thread_ && paused_mail_thread_->joinable())
410  paused_mail_thread_->join();
411  timer_->start();
412 
413  // update with changes since the last we were playing
414  subscriptions_div_->refresh_with_newest();
415  history_header_div_->flush_buffer();
416 }
417 
418 void goby::common::LiaisonScope::ControlsContainer::run_paused_mail()
419 {
420  while (is_paused_)
421  {
422  while (scope_->zeromq_service_->poll(10000)) {}
423  }
424 }
425 
426 goby::common::LiaisonScope::SubscriptionsContainer::SubscriptionsContainer(
427  LiaisonScope* node, Wt::WStandardItemModel* model, Wt::WStringListModel* history_model,
428  std::map<std::string, int>& msg_map, Wt::WContainerWidget* parent /*= 0*/)
429  : WContainerWidget(parent), node_(node), model_(model), history_model_(history_model),
430  msg_map_(msg_map), add_text_(new WText("Add subscription (e.g. NAV* or NAV_X): ", this)),
431  subscribe_filter_text_(new WLineEdit(this)),
432  subscribe_filter_button_(new WPushButton("Apply", this)), subscribe_break_(new WBreak(this)),
433  remove_text_(new WText("Subscriptions (click to remove): ", this))
434 {
435  subscribe_filter_button_->clicked().connect(this,
436  &SubscriptionsContainer::handle_add_subscription);
437  subscribe_filter_text_->enterPressed().connect(
438  this, &SubscriptionsContainer::handle_add_subscription);
439 }
440 
441 void goby::common::LiaisonScope::SubscriptionsContainer::handle_add_subscription()
442 {
443  add_subscription(subscribe_filter_text_->text().narrow());
444  subscribe_filter_text_->setText("");
445 }
446 
447 void goby::common::LiaisonScope::SubscriptionsContainer::add_subscription(std::string type)
448 {
449  boost::trim(type);
450  if (type.empty() || subscriptions_.count(type))
451  return;
452 
453  WPushButton* new_button = new WPushButton(this);
454 
455  new_button->setText(type + " ");
456  node_->subscribe(type, LIAISON_INTERNAL_SUBSCRIBE_SOCKET);
457 
458  new_button->clicked().connect(
459  boost::bind(&SubscriptionsContainer::handle_remove_subscription, this, new_button));
460 
461  subscriptions_.insert(type);
462 
463  refresh_with_newest(type);
464 }
465 
466 void goby::common::LiaisonScope::SubscriptionsContainer::refresh_with_newest()
467 {
468  for (std::set<std::string>::const_iterator it = subscriptions_.begin(),
469  end = subscriptions_.end();
470  it != end; ++it)
471  refresh_with_newest(*it);
472 }
473 
474 void goby::common::LiaisonScope::SubscriptionsContainer::refresh_with_newest(
475  const std::string& type)
476 {
477  std::vector<CMOOSMsg> newest = node_->newest_substr(type);
478  for (std::vector<CMOOSMsg>::iterator it = newest.begin(), end = newest.end(); it != end; ++it)
479  { node_->handle_message(*it, false); } }
480 
481 void goby::common::LiaisonScope::SubscriptionsContainer::handle_remove_subscription(
482  WPushButton* clicked_button)
483 {
484  std::string type_name = clicked_button->text().narrow();
485  boost::trim(type_name);
486  unsigned type_name_size = type_name.size();
487 
488  node_->unsubscribe(clicked_button->text().narrow(), LIAISON_INTERNAL_SUBSCRIBE_SOCKET);
489  subscriptions_.erase(type_name);
490 
491  bool has_wildcard_ending = (type_name[type_name_size - 1] == '*');
492  if (has_wildcard_ending)
493  type_name = type_name.substr(0, type_name_size - 1);
494 
495  for (int i = model_->rowCount() - 1, n = 0; i >= n; --i)
496  {
497  std::string text_to_match = model_->item(i, 0)->text().narrow();
498  boost::trim(text_to_match);
499 
500  bool remove = false;
501  if (has_wildcard_ending && boost::starts_with(text_to_match, type_name))
502  remove = true;
503  else if (!has_wildcard_ending && boost::equals(text_to_match, type_name))
504  remove = true;
505 
506  if (remove)
507  {
508  history_model_->removeRows(msg_map_[text_to_match], 1);
509  msg_map_.erase(text_to_match);
510  glog.is(DEBUG1) && glog << "LiaisonScope: removed " << text_to_match << std::endl;
511  model_->removeRow(i);
512 
513  // shift down the remaining indices
514  for (std::map<std::string, int>::iterator it = msg_map_.begin(), n = msg_map_.end();
515  it != n; ++it)
516  {
517  if (it->second > i)
518  --it->second;
519  }
520  }
521  }
522 
523  this->removeWidget(clicked_button);
524  delete clicked_button; // removeWidget does not delete
525 }
526 
527 goby::common::LiaisonScope::HistoryContainer::HistoryContainer(
528  MOOSNode* node, Wt::WVBoxLayout* main_layout, Wt::WAbstractItemModel* model,
529  const protobuf::MOOSScopeConfig& moos_scope_config, Wt::WContainerWidget* parent /* = 0 */)
530  : Wt::WContainerWidget(parent), node_(node), main_layout_(main_layout),
531  moos_scope_config_(moos_scope_config), hr_(new WText("<hr />", this)),
532  add_text_(new WText(("Add history for key: "), this)), history_box_(new WComboBox(this)),
533  history_button_(new WPushButton("Add", this)), buffer_(moos_scope_config.max_history_items())
534 
535 {
536  history_box_->setModel(model);
537  history_button_->clicked().connect(this, &HistoryContainer::handle_add_history);
538 }
539 
540 void goby::common::LiaisonScope::HistoryContainer::handle_add_history()
541 {
542  std::string selected_key = history_box_->currentText().narrow();
544  config.set_key(selected_key);
545  add_history(config);
546 }
547 
548 void goby::common::LiaisonScope::HistoryContainer::add_history(
550 {
551  const std::string& selected_key = config.key();
552 
553  if (!history_models_.count(selected_key))
554  {
555  Wt::WContainerWidget* new_container = new WContainerWidget;
556 
557  Wt::WContainerWidget* text_container = new WContainerWidget(new_container);
558  new WText("History for ", text_container);
559  WPushButton* remove_history_button = new WPushButton(selected_key, text_container);
560 
561  remove_history_button->clicked().connect(
562  boost::bind(&HistoryContainer::handle_remove_history, this, selected_key));
563 
564  new WText(" (click to remove)", text_container);
565  new WBreak(text_container);
566  // WPushButton* toggle_plot_button = new WPushButton("Plot", text_container);
567 
568  // text_container->resize(Wt::WLength::Auto, WLength(4, WLength::FontEm));
569 
570  Wt::WStandardItemModel* new_model =
571  new LiaisonScopeMOOSModel(moos_scope_config_, new_container);
572 
573  Wt::WSortFilterProxyModel* new_proxy = new Wt::WSortFilterProxyModel(new_container);
574  new_proxy->setSourceModel(new_model);
575 
576  // Chart::WCartesianChart* chart = new Chart::WCartesianChart(new_container);
577  // toggle_plot_button->clicked().connect(
578  // boost::bind(&HistoryContainer::toggle_history_plot, this, chart));
579  // chart->setModel(new_model);
580  // chart->setXSeriesColumn(protobuf::MOOSScopeConfig::COLUMN_TIME);
581  // Chart::WDataSeries s(protobuf::MOOSScopeConfig::COLUMN_VALUE, Chart::LineSeries);
582  // chart->addSeries(s);
583 
584  // chart->setType(Chart::ScatterPlot);
585  // chart->axis(Chart::XAxis).setScale(Chart::DateTimeScale);
586  // chart->axis(Chart::XAxis).setTitle("Time");
587  // chart->axis(Chart::YAxis).setTitle(selected_key);
588 
589  // WFont font;
590  // font.setFamily(WFont::Serif, "Gentium");
591  // chart->axis(Chart::XAxis).setTitleFont(font);
592  // chart->axis(Chart::YAxis).setTitleFont(font);
593 
594  // // Provide space for the X and Y axis and title.
595  // chart->setPlotAreaPadding(80, Left);
596  // chart->setPlotAreaPadding(40, Top | Bottom);
597  // chart->setMargin(10, Top | Bottom); // add margin vertically
598  // chart->setMargin(WLength::Auto, Left | Right); // center horizontally
599  // chart->resize(config.plot_width(), config.plot_height());
600 
601  // if(!config.show_plot())
602  // chart->hide();
603 
604  Wt::WTreeView* new_tree = new LiaisonScopeMOOSTreeView(moos_scope_config_, new_container);
605  main_layout_->insertWidget(main_layout_->count() - 2, new_container);
606  // set the widget *before* the one we just inserted to be resizable
607  main_layout_->setResizable(main_layout_->count() - 3);
608 
609  //main_layout_->insertWidget(main_layout_->count()-2, new_tree);
610  // set the widget *before* the one we just inserted to be resizable
611  //main_layout_->setResizable(main_layout_->count()-3);
612 
613  new_tree->setModel(new_proxy);
614  MVC& mvc = history_models_[selected_key];
615  mvc.key = selected_key;
616  mvc.container = new_container;
617  mvc.model = new_model;
618  mvc.tree = new_tree;
619  mvc.proxy = new_proxy;
620 
621  node_->zeromq_service()
622  ->socket_from_id(LIAISON_INTERNAL_SUBSCRIBE_SOCKET)
623  .set_blackout(MARSHALLING_MOOS, "CMOOSMsg/" + selected_key + "/",
624  boost::posix_time::milliseconds(0));
625 
626  new_proxy->setFilterRegExp(".*");
627  new_tree->sortByColumn(protobuf::MOOSScopeConfig::COLUMN_TIME, DescendingOrder);
628  }
629 }
630 
631 void goby::common::LiaisonScope::HistoryContainer::handle_remove_history(std::string key)
632 {
633  glog.is(DEBUG2) && glog << "LiaisonScope: removing history for: " << key << std::endl;
634 
635  main_layout_->removeWidget(history_models_[key].container);
636  main_layout_->removeWidget(history_models_[key].tree);
637 
638  history_models_.erase(key);
639 }
640 
641 void goby::common::LiaisonScope::HistoryContainer::toggle_history_plot(Wt::WWidget* plot)
642 {
643  if (plot->isHidden())
644  plot->show();
645  else
646  plot->hide();
647 }
648 
649 void goby::common::LiaisonScope::HistoryContainer::display_message(CMOOSMsg& msg)
650 {
651  std::map<std::string, HistoryContainer::MVC>::iterator hist_it =
652  history_models_.find(msg.GetKey());
653  if (hist_it != history_models_.end())
654  {
655  hist_it->second.model->appendRow(create_row(msg));
656  while (hist_it->second.model->rowCount() > moos_scope_config_.max_history_items())
657  hist_it->second.model->removeRow(0);
658 
659  hist_it->second.proxy->setFilterRegExp(".*");
660  }
661 }
662 
663 void goby::common::LiaisonScope::HistoryContainer::flush_buffer()
664 {
665  for (boost::circular_buffer<CMOOSMsg>::iterator it = buffer_.begin(), end = buffer_.end();
666  it != end; ++it)
667  { display_message(*it); } buffer_.clear();
668 }
669 
670 goby::common::LiaisonScope::RegexFilterContainer::RegexFilterContainer(
671  Wt::WStandardItemModel* model, Wt::WSortFilterProxyModel* proxy,
672  const protobuf::MOOSScopeConfig& moos_scope_config, Wt::WContainerWidget* parent /* = 0 */)
673  : Wt::WContainerWidget(parent), model_(model), proxy_(proxy), hr_(new WText("<hr />", this)),
674  set_text_(new WText(("Set regex filter: Column: "), this)),
675  regex_column_select_(new Wt::WComboBox(this)),
676  expression_text_(new WText((" Expression: "), this)),
677  regex_filter_text_(new WLineEdit(moos_scope_config.regex_filter_expression(), this)),
678  regex_filter_button_(new WPushButton("Set", this)),
679  regex_filter_clear_(new WPushButton("Clear", this)),
680  regex_filter_examples_(new WPushButton("Examples", this)), break_(new WBreak(this)),
681  regex_examples_table_(new WTable(this))
682 {
683  for (int i = 0, n = model_->columnCount(); i < n; ++i)
684  regex_column_select_->addItem(boost::any_cast<std::string>(model_->headerData(i)));
685  regex_column_select_->setCurrentIndex(moos_scope_config.regex_filter_column());
686 
687  regex_filter_button_->clicked().connect(this, &RegexFilterContainer::handle_set_regex_filter);
688  regex_filter_clear_->clicked().connect(this, &RegexFilterContainer::handle_clear_regex_filter);
689  regex_filter_text_->enterPressed().connect(this,
690  &RegexFilterContainer::handle_set_regex_filter);
691  regex_filter_examples_->clicked().connect(this,
692  &RegexFilterContainer::toggle_regex_examples_table);
693 
694  regex_examples_table_->setHeaderCount(1);
695  regex_examples_table_->setStyleClass("basic_small");
696  new WText("Expression", regex_examples_table_->elementAt(0, 0));
697  new WText("Matches", regex_examples_table_->elementAt(0, 1));
698  new WText(".*", regex_examples_table_->elementAt(1, 0));
699  new WText("anything", regex_examples_table_->elementAt(1, 1));
700  new WText(".*_STATUS", regex_examples_table_->elementAt(2, 0));
701  new WText("fields ending in \"_STATUS\"", regex_examples_table_->elementAt(2, 1));
702  new WText(".*[^(_STATUS)]", regex_examples_table_->elementAt(3, 0));
703  new WText("fields <i>not</i> ending in \"_STATUS\"", regex_examples_table_->elementAt(3, 1));
704  new WText("-?[[:digit:]]*\\.[[:digit:]]*e?-?[[:digit:]]*",
705  regex_examples_table_->elementAt(4, 0));
706  new WText("a floating point number (e.g. -3.456643e7)", regex_examples_table_->elementAt(4, 1));
707  regex_examples_table_->hide();
708 
709  handle_set_regex_filter();
710 }
711 
712 void goby::common::LiaisonScope::RegexFilterContainer::handle_set_regex_filter()
713 {
714  proxy_->setFilterKeyColumn(regex_column_select_->currentIndex());
715  proxy_->setFilterRegExp(regex_filter_text_->text());
716 }
717 
718 void goby::common::LiaisonScope::RegexFilterContainer::handle_clear_regex_filter()
719 {
720  regex_filter_text_->setText(".*");
721  handle_set_regex_filter();
722 }
723 
724 void goby::common::LiaisonScope::RegexFilterContainer::toggle_regex_examples_table()
725 {
726  regex_examples_table_->isHidden() ? regex_examples_table_->show()
727  : regex_examples_table_->hide();
728 }
Helpers for MOOS applications for serializing and parsed Google Protocol buffers messages.
common::FlexOstream glog
Access the Goby logger through this object.
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