diff --git a/insitu_plugins/CMakeLists.txt b/insitu_plugins/CMakeLists.txt index 320d2aa..00d0209 100644 --- a/insitu_plugins/CMakeLists.txt +++ b/insitu_plugins/CMakeLists.txt @@ -62,6 +62,8 @@ set(insitu_plugins_HDRS inc/Stats/Stats.hpp inc/Heartbeat/Heartbeat.hpp + + inc/Notification/Notification.hpp ) set(insitu_dialogs_HDRS @@ -74,6 +76,8 @@ set(insitu_dialogs_HDRS inc/Stats/Stats_dialog.hpp inc/Heartbeat/Heartbeat_dialog.hpp + + inc/Notification/Notification_dialog.hpp ) qt5_wrap_cpp(insitu_dialogs_MOCS ${insitu_dialogs_HDRS}) @@ -95,6 +99,11 @@ add_library(${PROJECT_NAME} src/Stats/Stats.cpp src/Stats/Stats_dialog.cpp + src/Heartbeat/Heartbeat.cpp + src/Heartbeat/Heartbeat_dialog.cpp + + src/Notification/Notification.cpp + src/Notification/Notification_dialog.cpp ) target_link_libraries(${PROJECT_NAME} diff --git a/insitu_plugins/README.md b/insitu_plugins/README.md index 9457c85..210bc39 100644 --- a/insitu_plugins/README.md +++ b/insitu_plugins/README.md @@ -16,11 +16,13 @@ filter developers. - a circle that turns red if a topic of interest stops publishing 5. Stats - displays CPU, memory, and disk usage +6. Notification + - display scrolling notification messages ## TODO: 1. Notifications - - A scrolling notification window with different alert levels + - Add different alert levels 2. Compass - Directional indicator for robots with GPS/Magnetometer capabilites 3. Movement Lines diff --git a/insitu_plugins/inc/Notification/Notification.hpp b/insitu_plugins/inc/Notification/Notification.hpp new file mode 100644 index 0000000..b207c5b --- /dev/null +++ b/insitu_plugins/inc/Notification/Notification.hpp @@ -0,0 +1,61 @@ +#ifndef insitu_plugins_Notification_HPP +#define insitu_plugins_Notification_HPP + +#include +#include +#include +#include "std_msgs/String.h" +#include + +namespace insitu_plugins +{ +class Notification : public insitu::Filter +{ +public: + Notification(void); + + const cv::Mat apply(void); + + bool hasSettingEditor(void) const + { + return true; + } + + void onTopicChange(const std::string& new_topic); + + void onQueueChange(const int new_queue_size); + + void onDirectionChange(const bool new_msg_direction_down); + +private: + void filterInit(void); + + void onDelete(void); + + void handleCallback(const std_msgs::String::ConstPtr& msg); + + std::string queueToString(std::queue str_queue); + + std::queue reverseQueue(std::queue str_queue); + + ros::NodeHandle nh_; + + std::string topic_name_; + + ros::Subscriber topic_subscriber_; + + cv::Mat ret_; + + unsigned int queue_size_; + + bool msg_direction_down_; + + std::queue msg_queue_; + + std::string msg_string_; + +}; // end class Notification + +} // end namespace insitu_plugins + +#endif // end insitu_plugins_Notification_HPP diff --git a/insitu_plugins/inc/Notification/Notification_dialog.hpp b/insitu_plugins/inc/Notification/Notification_dialog.hpp new file mode 100644 index 0000000..2a1e0d7 --- /dev/null +++ b/insitu_plugins/inc/Notification/Notification_dialog.hpp @@ -0,0 +1,43 @@ +#ifndef insitu_plugins_Notification_DIALOG_HPP +#define insitu_plugins_Notification_DIALOG_HPP + +#include + +namespace notification_filter +{ +const std::string DEFAULT_NAME = "Notification"; +const std::string DEFAULT_TOPIC = "notification_topic"; +const unsigned int DEFAULT_QUEUE_SIZE = 10; +} // end namespace notification_filter + +namespace insitu_plugins +{ +class NotificationDialog : public insitu::FilterDialog +{ + Q_OBJECT +private: + QGridLayout* layout; + QErrorMessage* error_msg; + + QLineEdit* nameEdit; + QLabel* nameLabel; + QLineEdit* topicEdit; + QLabel* topicLabel; + QDoubleSpinBox* queueBox; + QLabel* queueLabel; + QCheckBox* dirBox; + + QPushButton* okButton; + QPushButton* cancelButton; + +public Q_SLOTS: + + void onOK(void); + +public: + NotificationDialog(insitu::Filter* parent_); +}; + +} // end namespace insitu_plugins + +#endif // end insitu_plugins_Notification_DIALOG_HPP diff --git a/insitu_plugins/plugins.xml b/insitu_plugins/plugins.xml index 5b80a73..bd73ba3 100644 --- a/insitu_plugins/plugins.xml +++ b/insitu_plugins/plugins.xml @@ -8,13 +8,13 @@ testing new transparency filters - - display scrolling notifications - basic computer statistics Check to see if a topic is published + + Display scrolling notification messages + diff --git a/insitu_plugins/src/Notification/Notification.cpp b/insitu_plugins/src/Notification/Notification.cpp new file mode 100644 index 0000000..db832ad --- /dev/null +++ b/insitu_plugins/src/Notification/Notification.cpp @@ -0,0 +1,199 @@ +#include +#include +#include + +using namespace notification_filter; + +namespace insitu_plugins +{ +/* + Filter Implementation +*/ + +Notification::Notification(void) +{ + // TODO instantiation code +} + +void Notification::filterInit(void) +{ + settingsDialog = new NotificationDialog(this); + setSize(QSize(300, 300)); + + nh_ = getNodeHandle(); + + topic_name_ = getSettingsValue().get("topic", DEFAULT_TOPIC).asString(); + + topic_subscriber_ = // 2nd parameter is queue size + nh_.subscribe(topic_name_, 1, &Notification::handleCallback, this); + + queue_size_ = + getSettingsValue().get("queue_size", DEFAULT_QUEUE_SIZE).asInt(); + + msg_direction_down_ = + getSettingsValue().get("msg_direction_down", true).asBool(); + + msg_string_ = queueToString(msg_queue_); + + // TODO ROS initialization code +} + +void Notification::onDelete(void) +{ + topic_subscriber_.shutdown(); +} + +const cv::Mat Notification::apply(void) +{ + /* + Create a transparent image to construct your overlay on + */ + ret_ = cv::Mat(height(), width(), CV_8UC4, cv::Scalar(255, 255, 255, 0)); + + insitu_utils::Painter::drawtorect_multiline( + ret_, cv::Rect(0, 0, ret_.cols, ret_.rows), msg_string_, + queue_size_ + 2); + + return ret_; +} + +void Notification::handleCallback(const std_msgs::String::ConstPtr& msg) +{ + /* + Update queue with the most recent messages + */ + if (msg_queue_.size() >= queue_size_) + { + msg_queue_.pop(); + } + msg_queue_.push(msg->data); + msg_string_ = queueToString(msg_queue_); +} + +std::string Notification::queueToString(std::queue str_queue) +{ + std::queue q = str_queue; + std::string str; + int topic_name_len = std::round(topic_name_.length() * + 0.67); // 0.7 makes the the widths match + + /* + Add topic name and divider + */ + str += topic_name_ + "\n"; + for (int i = 0; i < topic_name_len; i++) + { + str += "-"; + } + str += "\n"; + + /* + Check new message direction + */ + if (!msg_direction_down_) + { + q = reverseQueue(q); + } + + /* + Add queue messages + */ + while (!q.empty()) + { + str += q.front() + "\n"; + q.pop(); + } + + return str; +} + +std::queue +Notification::reverseQueue(std::queue str_queue) +{ + std::queue q = str_queue; + std::stack s; + + /* + Fill stack + */ + while (!q.empty()) + { + s.push(q.front()); + q.pop(); + } + + /* + Fill queue + */ + while (!s.empty()) + { + q.push(s.top()); + s.pop(); + } + + return q; +} + +void Notification::onTopicChange(const std::string& new_topic) +{ + if (topic_name_ != new_topic) + { + /* + Update topic name + */ + try + { + topic_name_ = new_topic; + topic_subscriber_ = // 2nd parameter is queue size + nh_.subscribe(topic_name_, 1, &Notification::handleCallback, + this); + } + catch (ros::Exception& e) + { + ROS_ERROR("Error occured: %s ", e.what()); + } + + /* + Clear queue + */ + std::queue().swap(msg_queue_); + msg_string_ = queueToString(msg_queue_); + } +} + +void Notification::onQueueChange(const int new_queue_size) +{ + if (queue_size_ != new_queue_size) + { + /* + Update queue size + */ + queue_size_ = new_queue_size; + + /* + Clear queue + */ + std::queue().swap(msg_queue_); + msg_string_ = queueToString(msg_queue_); + } +} + +void Notification::onDirectionChange(const bool new_msg_direction_down) +{ + if (msg_direction_down_ != new_msg_direction_down) + { + /* + Update new message direction + */ + msg_direction_down_ = new_msg_direction_down; + + /* + Update string + */ + msg_string_ = queueToString(msg_queue_); + } +} + +} // end namespace insitu_plugins + +PLUGINLIB_EXPORT_CLASS(insitu_plugins::Notification, insitu::Filter); diff --git a/insitu_plugins/src/Notification/Notification_dialog.cpp b/insitu_plugins/src/Notification/Notification_dialog.cpp new file mode 100644 index 0000000..926f3e6 --- /dev/null +++ b/insitu_plugins/src/Notification/Notification_dialog.cpp @@ -0,0 +1,72 @@ +#include +#include + +using namespace notification_filter; + +namespace insitu_plugins +{ +NotificationDialog::NotificationDialog(insitu::Filter* parent_) + : FilterDialog(parent_) +{ + layout = new QGridLayout(); + + error_msg = new QErrorMessage(); + + nameEdit = new QLineEdit(QString::fromStdString(DEFAULT_NAME)); + nameLabel = new QLabel(tr("Name: "), nameEdit); + layout->addWidget(nameLabel, 0, 0); + layout->addWidget(nameEdit, 0, 1); + + topicEdit = new QLineEdit(QString::fromStdString(DEFAULT_TOPIC)); + topicLabel = new QLabel(tr("Topic: "), topicEdit); + layout->addWidget(topicLabel, 1, 0); + layout->addWidget(topicEdit, 1, 1); + + queueBox = new QDoubleSpinBox(); + queueBox->setRange(2, 15); + queueBox->setSingleStep(1); + queueBox->setDecimals(0); + queueBox->setValue(DEFAULT_QUEUE_SIZE); + queueLabel = new QLabel(tr("Number of Messages")); + layout->addWidget(queueLabel, 2, 0); + layout->addWidget(queueBox, 2, 1); + + dirBox = new QCheckBox(tr("New Messages at the Bottom"), this); + dirBox->setChecked(true); + layout->addWidget(dirBox, 3, 0, 1, 2); + + cancelButton = new QPushButton(tr("Cancel")); + layout->addWidget(cancelButton, 4, 0); + + okButton = new QPushButton(tr("OK")); + okButton->setDefault(true); + layout->addWidget(okButton, 4, 1); + + setLayout(layout); + + QObject::connect(okButton, SIGNAL(clicked()), SLOT(onOK())); + QObject::connect(cancelButton, SIGNAL(clicked()), SLOT(reject())); +} + +void NotificationDialog::onOK(void) +{ + Json::Value& settings = parent->getSettingsValue(); + + settings["name"] = nameEdit->text().toStdString(); + + static_cast(parent)->onTopicChange( + topicEdit->text().toStdString()); + settings["topic"] = topicEdit->text().toStdString(); + + static_cast(parent)->onQueueChange( + (unsigned int)queueBox->value()); + settings["queue_size"] = (unsigned int)queueBox->value(); + + static_cast(parent)->onDirectionChange( + dirBox->isChecked()); + settings["msg_direction_down"] = dirBox->isChecked(); + + accept(); +} + +} // end namespace insitu_plugins diff --git a/insitu_utils/lib/insitu_utils/painter.hpp b/insitu_utils/lib/insitu_utils/painter.hpp index e91206a..6088da0 100644 --- a/insitu_utils/lib/insitu_utils/painter.hpp +++ b/insitu_utils/lib/insitu_utils/painter.hpp @@ -157,6 +157,12 @@ class Painter const std::string& str, int face = cv::FONT_HERSHEY_PLAIN, int thickness = 1, cv::Scalar color = Color::white); + + static void drawtorect_multiline(cv::Mat& mat, cv::Rect target, + const std::string& str, int num_lines, + int face = cv::FONT_HERSHEY_PLAIN, + int thickness = 1, + cv::Scalar color = Color::white); }; } // namespace insitu_utils diff --git a/insitu_utils/src/painter.cpp b/insitu_utils/src/painter.cpp index 790536a..3c5000a 100644 --- a/insitu_utils/src/painter.cpp +++ b/insitu_utils/src/painter.cpp @@ -24,4 +24,38 @@ void Painter::drawtorect(cv::Mat& mat, cv::Rect target, const std::string& str, scale, color, thickness, cv::LINE_AA, false); } +void Painter::drawtorect_multiline(cv::Mat& mat, cv::Rect target, + const std::string& str, int num_lines, + int face, int thickness, cv::Scalar color) +{ + double scaley = (double)target.height / num_lines; + double scale = scaley * 0.5 / 10; // Letter height is smaller to leave + // whitespace between lines + int marginx = num_lines / 3; // Prevent first letter from getting cut off + int marginy = scaley; + + /* + Print multiple lines of text + */ + std::string text = str; + std::string line; + int line_height = marginy * 0.7; + std::size_t found = text.find_first_of('\n'); + + while (found != std::string::npos) + { // Loop to print every line + line = text.substr(0, found); + cv::putText(mat, line, + cv::Point(target.x + marginx, target.y + line_height), face, + scale, color, thickness, cv::LINE_AA, false); + + line_height += marginy; // Position for next line + text = text.substr(found + 1, std::string::npos); + found = text.find_first_of('\n'); + } + cv::putText(mat, text, + cv::Point(target.x + marginx, target.y + marginy + line_height), + face, scale, color, thickness, cv::LINE_AA, false); +} + } // namespace insitu_utils