/*
 * Copyright (c) 2008, Willow Garage, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of Willow Garage, Inc. nor the names of its
 *       contributors may be used to endorse or promote products derived from
 *       this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "rosout_list_control.h"
#include "rosout_generated.h"
#include "rosout_panel.h"
#include "rosout_text_filter.h"

#include <ros/assert.h>

#include <boost/algorithm/string/join.hpp>
#include <boost/regex.hpp>

#include <wx/imaglist.h>
#include <wx/artprov.h>
#include <wx/clipbrd.h>

#include <sstream>

namespace rxtools
{

RosoutListControl::RosoutListControl(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style, const wxValidator& validator,
                                     const wxString& name)
: wxListCtrl(parent, id, pos, size, style, validator, name)
, last_selection_(-1)
, scrollbar_at_bottom_(true)
, disable_scroll_to_bottom_(false)
#if __WXMAC__
, manual_selection_(false)
#endif
{
  wxListItem item;
  item.SetText(wxT("Message"));
  item.SetWidth(600);
  InsertColumn(columns::Message, item);
  item.SetText(wxT("Severity"));
  item.SetWidth(100);
  InsertColumn(columns::Severity, item);
  item.SetText(wxT("Node"));
  item.SetWidth(200);
  InsertColumn(columns::Node, item);
  item.SetText(wxT("Time"));
  item.SetWidth(200);
  InsertColumn(columns::Time, item);
  item.SetText(wxT("Topics"));
  item.SetWidth(200);
  InsertColumn(columns::Topics, item);
  item.SetText(wxT("Location"));
  item.SetWidth(600);
  InsertColumn(columns::Location, item);


  wxImageList* image_list = new wxImageList(16, 16);
  fatal_image_id_ = image_list->Add(wxArtProvider::GetIcon(wxART_CROSS_MARK, wxART_OTHER, wxSize(16, 16)));
  error_image_id_ = image_list->Add(wxArtProvider::GetIcon(wxART_ERROR, wxART_OTHER, wxSize(16, 16)));
  warning_image_id_ = image_list->Add(wxArtProvider::GetIcon(wxART_WARNING, wxART_OTHER, wxSize(16, 16)));
  info_image_id_ = image_list->Add(wxArtProvider::GetIcon(wxART_INFORMATION, wxART_OTHER, wxSize(16, 16)));
  debug_image_id_ = image_list->Add(wxArtProvider::GetIcon(wxART_HELP, wxART_OTHER, wxSize(16, 16)));

  AssignImageList(image_list, wxIMAGE_LIST_SMALL);

  Connect(wxEVT_COMMAND_LIST_ITEM_ACTIVATED, wxListEventHandler(RosoutListControl::onItemActivated), NULL, this);
  Connect(wxEVT_COMMAND_LIST_ITEM_RIGHT_CLICK, wxListEventHandler(RosoutListControl::onItemRightClick), NULL, this);
  Connect(wxEVT_COMMAND_LIST_ITEM_SELECTED, wxListEventHandler(RosoutListControl::onItemSelected), NULL, this);
  Connect(wxEVT_CHAR, wxKeyEventHandler(RosoutListControl::onChar));

}

RosoutListControl::~RosoutListControl()
{
}

void RosoutListControl::setModel(RosoutPanel* model)
{
  model_ = model;
}

wxString RosoutListControl::getSeverityText(const rosgraph_msgs::LogConstPtr& message) const
{
  switch (message->level)
  {
  case rosgraph_msgs::Log::DEBUG:
    return wxT("Debug");
  case rosgraph_msgs::Log::INFO:
    return wxT("Info");
  case rosgraph_msgs::Log::WARN:
    return wxT("Warn");
  case rosgraph_msgs::Log::ERROR:
    return wxT("Error");
  case rosgraph_msgs::Log::FATAL:
    return wxT("Fatal");
  }

  return wxT("Unknown");
}

int RosoutListControl::OnGetItemImage(long item) const
{
  ROS_ASSERT(model_);

  rosgraph_msgs::LogConstPtr message = model_->getMessageByIndex(item);
  if (!message)
  {
    return -1;
  }

  switch (message->level)
  {
  case rosgraph_msgs::Log::DEBUG:
    return debug_image_id_;
  case rosgraph_msgs::Log::INFO:
    return info_image_id_;
  case rosgraph_msgs::Log::WARN:
    return warning_image_id_;
  case rosgraph_msgs::Log::ERROR:
    return error_image_id_;
  case rosgraph_msgs::Log::FATAL:
    return fatal_image_id_;
  }

  return -1;
}

wxListItemAttr * RosoutListControl::OnGetItemAttr(long item) const
{
#if 0
  ROS_ASSERT(model_);

  const rosgraph_msgs::Log& message = model_->getMessageByIndex(item);

  switch( message->level )
  {
    case rosgraph_msgs::Log::DEBUG:
    attr_.SetBackgroundColour( wxColour( 204, 255, 204 ) );
    break;
    case rosgraph_msgs::Log::INFO:
    attr_.SetBackgroundColour( *wxWHITE );
    break;
    case rosgraph_msgs::Log::WARN:
    attr_.SetBackgroundColour( wxColour( 255, 255, 153 ) );
    break;
    case rosgraph_msgs::Log::ERROR:
    attr_.SetBackgroundColour( wxColour( 255, 153, 0 ) );
    break;
    case rosgraph_msgs::Log::FATAL:
    attr_.SetBackgroundColour( *wxRED );
    break;
    default:
    ROS_BREAK();
  }
#endif

  return &attr_;
}

wxString RosoutListControl::OnGetItemText(long item, long column) const
{
  ROS_ASSERT(model_);

  rosgraph_msgs::LogConstPtr message = model_->getMessageByIndex(item);
  if (!message)
  {
    return wxString();
  }

  switch (column)
  {
  case columns::Severity:
  {
    return getSeverityText(message);
  }
    break;
  case columns::Time:
  {
    std::stringstream ss;
    ss << message->header.stamp;
    return wxString::FromAscii(ss.str().c_str());
  }
  case columns::Message:
  {
    std::string msg = message->msg;
    size_t pos = std::string::npos;
    while (true)
    {
      pos = msg.find('\n');
      if (pos == std::string::npos)
      {
        break;
      }

      msg.replace(pos, 1, "\\n");
    }
    while (true)
    {
      pos = msg.find('\r');
      if (pos == std::string::npos)
      {
        break;
      }

      msg.replace(pos, 1, "\\r");
    }
    return wxString::FromAscii(msg.c_str());
  }
  case columns::Topics:
  {
    std::stringstream ss;
    typedef std::vector<std::string> V_string;
    V_string::const_iterator it = message->topics.begin();
    V_string::const_iterator end = message->topics.end();
    for (; it != end; ++it)
    {
      if (it != message->topics.begin())
      {
        ss << ", ";
      }

      ss << *it;
    }

    return wxString::FromAscii(ss.str().c_str());
  }
  case columns::Location:
  {
    wxString str;
    str << wxString::FromAscii(message->file.c_str()) << wxT(":") << wxString::FromAscii(message->function.c_str()) << wxT(":") << message->line;
    return str;
  }
  case columns::Node:
  {
    return wxString::FromAscii(message->name.c_str());
  }
  }

  ROS_BREAK();
  return wxT("Unknown Column");
}

void TextboxDialog::onChar(wxKeyEvent& event)
{
  int key = event.GetKeyCode();
  if (key == WXK_ESCAPE)
  {
    wxObject* obj = event.GetEventObject();
    wxWindow* win = wxDynamicCast(obj, wxWindow);
    if (win)
    {
      if (win->GetParent())
      {
        win->GetParent()->Close();
      }
      else
      {
        win->Close();
      }
    }
  }
  // For some reason the built-in ctrl-C handling doesn't work in linux
  else if (key == 3)
  {
    wxObject* obj = event.GetEventObject();
    wxRichTextCtrl* text = wxDynamicCast(obj, wxRichTextCtrl);
    if (text)
    {
      text->Copy();
    }

    event.Skip();
  }
  else
  {
    event.Skip();
  }
}

void RosoutListControl::onItemActivated(wxListEvent& event)
{
  ROS_ASSERT(model_);

  rosgraph_msgs::LogConstPtr message = model_->getMessageByIndex(event.GetIndex());
  if (!message)
  {
    return;
  }

  TextboxDialog* dialog = new TextboxDialog(this, wxID_ANY);
  dialog->Show();
  dialog->text_->SetFocus();

  wxRichTextCtrl& t = *dialog->text_;

  // Node name
  {
    t.BeginBold();
    t.WriteText(wxT("Node: "));
    t.EndBold();
    t.WriteText(wxString::FromAscii(message->name.c_str()));
    t.Newline();
  }

  // Time
  {
    t.BeginBold();
    t.WriteText(wxT("Time: "));
    t.EndBold();
    std::stringstream ss;
    ss << message->header.stamp;
    t.WriteText(wxString::FromAscii(ss.str().c_str()));
    t.Newline();
  }

  // Severity
  {
    t.BeginBold();
    t.WriteText(wxT("Severity: "));
    t.EndBold();
    t.WriteText(getSeverityText(message));
    t.Newline();
  }

  // Location
  {
    if (!message->file.empty())
    {
      t.BeginBold();
      t.WriteText(wxT("Location: "));
      t.EndBold();
      std::stringstream ss;
      ss << message->file << ":" << message->function << ":" << message->line;
      t.WriteText(wxString::FromAscii(ss.str().c_str()));
      t.Newline();
    }
  }

  // Published Topics
  {
    t.BeginBold();
    t.WriteText(wxT("Published Topics: "));
    t.EndBold();
    t.WriteText(wxString::FromAscii(boost::join(message->topics, ", ").c_str()));
    t.Newline();
  }

  // Message
  {
    t.Newline();
    t.BeginTextColour(wxColour(127, 61, 2));
    t.BeginBold();
    t.WriteText(wxString::FromAscii(message->msg.c_str()));
    t.EndTextColour();
    t.EndBold();
  }
}

std::string escapeForRegex(const std::string& str)
{
  const static boost::regex esc("[\\^\\.\\$\\|\\(\\)\\[\\]\\*\\+\\?\\/\\\\]");
  const static std::string rep("\\\\\\1&");
  return boost::regex_replace(str, esc, rep, boost::match_default | boost::format_sed);
}

void addFilter(RosoutPanel* model, const std::string& text, uint32_t field_mask, bool include, bool new_window)
{
  if (new_window)
  {
    RosoutFrame* frame = model->createNewFrame();
    model = frame->rosout_panel_;
    model->clearFilters();
  }

  RosoutTextFilterPtr filter = model->createTextFilter();
  filter->setFilterType(include ? RosoutTextFilter::Include : RosoutTextFilter::Exclude);
  filter->setFieldMask(field_mask);
  filter->setText("^" + escapeForRegex(text) + "$");
  filter->setUseRegex(true);
  model->refilter();
}

rosgraph_msgs::LogConstPtr RosoutListControl::getSelectedMessage()
{
  if (last_selection_ == -1)
  {
    return rosgraph_msgs::LogConstPtr();
  }

  return model_->getMessageByIndex(last_selection_);
}

void RosoutListControl::onExcludeLocation(wxCommandEvent& event)
{
  if (rosgraph_msgs::LogConstPtr message = getSelectedMessage())
  {
    std::stringstream ss;
    ss << message->file << ":" << message->function << ":" << message->line;
    addFilter(model_, ss.str(), RosoutTextFilter::Location, false, false);
  }
}

void RosoutListControl::onExcludeNode(wxCommandEvent& event)
{
  if (rosgraph_msgs::LogConstPtr message = getSelectedMessage())
  {
    addFilter(model_, message->name, RosoutTextFilter::Node, false, false);
  }
}

void RosoutListControl::onExcludeMessage(wxCommandEvent& event)
{
  if (rosgraph_msgs::LogConstPtr message = getSelectedMessage())
  {
    addFilter(model_, message->msg, RosoutTextFilter::Message, false, false);
  }
}

void RosoutListControl::onExcludeLocationNewWindow(wxCommandEvent& event)
{
  if (rosgraph_msgs::LogConstPtr message = getSelectedMessage())
  {
    std::stringstream ss;
    ss << message->file << ":" << message->function << ":" << message->line;
    addFilter(model_, ss.str(), RosoutTextFilter::Location, false, true);
  }
}

void RosoutListControl::onExcludeNodeNewWindow(wxCommandEvent& event)
{
  if (rosgraph_msgs::LogConstPtr message = getSelectedMessage())
  {
    addFilter(model_, message->name, RosoutTextFilter::Node, false, true);
  }
}

void RosoutListControl::onExcludeMessageNewWindow(wxCommandEvent& event)
{
  if (rosgraph_msgs::LogConstPtr message = getSelectedMessage())
  {
    addFilter(model_, message->msg, RosoutTextFilter::Message, false, true);
  }
}

void RosoutListControl::onIncludeLocation(wxCommandEvent& event)
{
  if (rosgraph_msgs::LogConstPtr message = getSelectedMessage())
  {
    std::stringstream ss;
    ss << message->file << ":" << message->function << ":" << message->line;
    addFilter(model_, ss.str(), RosoutTextFilter::Location, true, false);
  }
}

void RosoutListControl::onIncludeNode(wxCommandEvent& event)
{
  if (rosgraph_msgs::LogConstPtr message = getSelectedMessage())
  {
    addFilter(model_, message->name, RosoutTextFilter::Node, true, false);
  }
}

void RosoutListControl::onIncludeMessage(wxCommandEvent& event)
{
  if (rosgraph_msgs::LogConstPtr message = getSelectedMessage())
  {
    addFilter(model_, message->msg, RosoutTextFilter::Message, true, false);
  }
}

void RosoutListControl::onIncludeLocationNewWindow(wxCommandEvent& event)
{
  if (rosgraph_msgs::LogConstPtr message = getSelectedMessage())
  {
    std::stringstream ss;
    ss << message->file << ":" << message->function << ":" << message->line;
    addFilter(model_, ss.str(), RosoutTextFilter::Location, true, true);
  }
}

void RosoutListControl::onIncludeNodeNewWindow(wxCommandEvent& event)
{
  if (rosgraph_msgs::LogConstPtr message = getSelectedMessage())
  {
    addFilter(model_, message->name, RosoutTextFilter::Node, true, true);
  }
}

void RosoutListControl::onIncludeMessageNewWindow(wxCommandEvent& event)
{
  if (rosgraph_msgs::LogConstPtr message = getSelectedMessage())
  {
    addFilter(model_, message->msg, RosoutTextFilter::Message, true, true);
  }
}

void RosoutListControl::copySelectionToClipboard(bool message_only)
{
  updateSelection();

  std::stringstream ss;
  S_int32::const_iterator it = selection_.begin();
  S_int32::const_iterator end = selection_.end();
  for (; it != end; ++it)
  {
    int32_t index = *it;

    if (it != selection_.begin())
    {
      ss << std::endl << std::endl;
    }

    rosgraph_msgs::LogConstPtr message = model_->getMessageByIndex(index);
    if (message)
    {
      if (message_only)
      {
        ss << message->msg;
      }
      else
      {
        ss << *message;
      }
    }
  }

  if (wxTheClipboard->Open())
  {
    wxTheClipboard->SetData(new wxTextDataObject(wxString::FromAscii(ss.str().c_str())));
    wxTheClipboard->Close();
  }
}

void RosoutListControl::onCopy(wxCommandEvent& event)
{
  copySelectionToClipboard(false);
}

void RosoutListControl::onCopyMessageOnly(wxCommandEvent& event)
{
  copySelectionToClipboard(true);
}

void RosoutListControl::onChar(wxKeyEvent& event)
{
  int key = event.GetKeyCode();
  // Ctrl-C (copy)
  if (key == 3)
  {
    copySelectionToClipboard(false);
    event.Skip();
  }
}

void RosoutListControl::onItemRightClick(wxListEvent& event)
{
  updateSelection();
  ROS_ASSERT(model_);

  wxMenu* menu = new wxMenu(wxT(""));
  wxMenuItem* item = 0;

  item = menu->Append(wxID_COPY, wxT("&Copy\tCtrl+C"));
  Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(RosoutListControl::onCopy));
  item = menu->Append(wxID_ANY, wxT("Copy &Message Only"));
  Connect(item->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(RosoutListControl::onCopyMessageOnly), NULL, this);

  // Can only create new filters on 1 message at a time
  if (selection_.size() == 1)
  {
    rosgraph_msgs::LogConstPtr message = model_->getMessageByIndex(event.GetIndex());
    if (message)
    {
      wxMenu* exclude_menu = new wxMenu(wxT(""));
      wxMenu* include_menu = new wxMenu(wxT(""));

      // Setup the include menu
      {
        if (!message->file.empty())
        {
          item = include_menu->Append(wxID_ANY, wxT("This Location"));
          Connect(item->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(RosoutListControl::onIncludeLocation), NULL, this);
          item = include_menu->Append(wxID_ANY, wxT("This Location (New Window)"));
          Connect(item->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(RosoutListControl::onIncludeLocationNewWindow), NULL, this);
        }

        item = include_menu->Append(wxID_ANY, wxT("This Node"));
        Connect(item->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(RosoutListControl::onIncludeNode), NULL, this);
        item = include_menu->Append(wxID_ANY, wxT("This Node (New Window)"));
        Connect(item->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(RosoutListControl::onIncludeNodeNewWindow), NULL, this);
        item = include_menu->Append(wxID_ANY, wxT("This Message"));
        Connect(item->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(RosoutListControl::onIncludeMessage), NULL, this);
        item = include_menu->Append(wxID_ANY, wxT("This Message (New Window)"));
        Connect(item->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(RosoutListControl::onIncludeMessageNewWindow), NULL, this);

        menu->AppendSubMenu(include_menu, wxT("&Include"));
      }

      // Setup the exclude menu
      {
        if (!message->file.empty())
        {
          item = exclude_menu->Append(wxID_ANY, wxT("This Location"));
          Connect(item->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(RosoutListControl::onExcludeLocation), NULL, this);
          item = exclude_menu->Append(wxID_ANY, wxT("This Location (New Window)"));
          Connect(item->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(RosoutListControl::onExcludeLocationNewWindow), NULL, this);
        }

        item = exclude_menu->Append(wxID_ANY, wxT("This Node"));
        Connect(item->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(RosoutListControl::onExcludeNode), NULL, this);
        item = exclude_menu->Append(wxID_ANY, wxT("This Node (New Window)"));
        Connect(item->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(RosoutListControl::onExcludeNodeNewWindow), NULL, this);
        item = exclude_menu->Append(wxID_ANY, wxT("This Message"));
        Connect(item->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(RosoutListControl::onExcludeMessage), NULL, this);
        item = exclude_menu->Append(wxID_ANY, wxT("This Message (New Window)"));
        Connect(item->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(RosoutListControl::onExcludeMessageNewWindow), NULL, this);

        menu->AppendSubMenu(exclude_menu, wxT("&Exclude"));
      }
    }
  }

  PopupMenu(menu);
}

void RosoutListControl::updateSelection()
{
  selection_.clear();

  int32_t index = -1;
  while (true)
  {
    index = GetNextItem(index, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
    if (index == -1)
    {
      break;
    }

    selection_.insert(index);
  }
}

void RosoutListControl::onItemSelected(wxListEvent& event)
{
  last_selection_ = event.GetIndex();

  updateSelection();

#if __WXMAC__
  if (!manual_selection_)
  {
#endif

  disable_scroll_to_bottom_ = true;

#if __WXMAC__
  }
  manual_selection_ = false;
#endif
}

void RosoutListControl::preItemChanges()
{
  /// @todo wxListCtrl::GetScrollRange doesn't work, so I have to work around it.  Switch to use GetScrollPos and GetScrollRange once Bug #10155 in the wxWidgets trac is fixed.
  scrollbar_at_bottom_ = false;
  int count_per_page = GetCountPerPage();
  int scroll_pos = GetScrollPos(wxVERTICAL);
#if __WXMAC__
  // wxListCtrl::GetScrollPos has different behavior on OSX, adjust for that
  //--count_per_page;
  int32_t item_height = 20;
  if (GetItemCount() > 0)
  {
    wxRect rect;
    if (GetItemRect(0, rect))
    {
      // For some reason this is always returning -1 right now, so we default to 20 (above)
      if (rect.GetHeight() > 0)
      {
        item_height = rect.GetHeight();
      }
    }
  }

  scroll_pos /= item_height;
#endif
  if (scroll_pos + count_per_page >= GetItemCount())
  {
    scrollbar_at_bottom_ = true;
  }

  Freeze();
}

void RosoutListControl::postItemChanges()
{
  if (!disable_scroll_to_bottom_ && scrollbar_at_bottom_ && GetItemCount() > 0)
  {
    EnsureVisible(GetItemCount() - 1);
  }

  disable_scroll_to_bottom_ = false;

  // OSX has a problem where it unselects whatever was selected after adding items.
#if __WXMAC__
  setSelection(selection_);
#endif

  Thaw();

  // This for some reason prevents the control from flickering: http://wiki.wxwidgets.org/Flicker-Free_Drawing#No-flickering_for_wxListCtrl_with_wxLC_REPORT_.7C_wxLC_VIRTUAL_style
  wxIdleEvent idle;
  wxTheApp->SendIdleEvents(this, idle);
}

void RosoutListControl::setSelection(const S_int32& sel)
{
#if __WXMAC__
  manual_selection_ = true;
#endif

  // Select anything in the new selection set
  {
    S_int32::const_iterator it = sel.begin();
    S_int32::const_iterator end = sel.end();
    for (; it != end; ++it)
    {
      int32_t index = *it;
      ROS_ASSERT(index >= 0);

      SetItemState(index, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
    }
  }

  // Deselect anything that was selected but is no longer
  {
    S_int32::const_iterator it = selection_.begin();
    S_int32::const_iterator end = selection_.end();
    for (; it != end; ++it)
    {
      int32_t index = *it;
      ROS_ASSERT(index >= 0);

      SetItemState(index, 0, wxLIST_STATE_SELECTED|wxLIST_STATE_FOCUSED);
    }
  }

  selection_ = sel;
}

} // namespace rxtools
