00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031 #include <stdio.h>
00032 #include <algorithm>
00033 #include <iterator>
00034
00035 #include <ros/time.h>
00036 #include <rosbag/bag.h>
00037
00038 #include <swri_console/log_database_proxy_model.h>
00039 #include <swri_console/log_database.h>
00040 #include <swri_console/settings_keys.h>
00041
00042 #include <QColor>
00043 #include <QFile>
00044 #include <QTextStream>
00045 #include <QTimer>
00046 #include <QSettings>
00047 #include <QtGlobal>
00048
00049 namespace swri_console
00050 {
00051 LogDatabaseProxyModel::LogDatabaseProxyModel(LogDatabase *db)
00052 :
00053 colorize_logs_(true),
00054 display_time_(true),
00055 display_absolute_time_(false),
00056 use_regular_expressions_(false),
00057 debug_color_(Qt::gray),
00058 info_color_(Qt::black),
00059 warn_color_(QColor(255,127,0)),
00060 error_color_(Qt::red),
00061 fatal_color_(Qt::magenta),
00062 db_(db),
00063 failedSearchText_(""),
00064 failedSearchIndex_(0)
00065 {
00066 QObject::connect(db_, SIGNAL(databaseCleared()),
00067 this, SLOT(handleDatabaseCleared()));
00068 QObject::connect(db_, SIGNAL(messagesAdded()),
00069 this, SLOT(processNewMessages()));
00070
00071 QObject::connect(db_, SIGNAL(minTimeUpdated()),
00072 this, SLOT(minTimeUpdated()));
00073 }
00074
00075 LogDatabaseProxyModel::~LogDatabaseProxyModel()
00076 {
00077 }
00078
00079 void LogDatabaseProxyModel::setNodeFilter(const std::set<std::string> &names)
00080 {
00081 names_ = names;
00082 reset();
00083 }
00084
00085 void LogDatabaseProxyModel::setSeverityFilter(uint8_t severity_mask)
00086 {
00087 severity_mask_ = severity_mask;
00088 reset();
00089 }
00090
00091 void LogDatabaseProxyModel::setAbsoluteTime(bool absolute)
00092 {
00093 if (absolute == display_absolute_time_) {
00094 return;
00095 }
00096
00097 display_absolute_time_ = absolute;
00098
00099 QSettings settings;
00100 settings.setValue(SettingsKeys::ABSOLUTE_TIMESTAMPS, display_absolute_time_);
00101
00102 if (display_time_ && msg_mapping_.size()) {
00103 Q_EMIT dataChanged(index(0), index(msg_mapping_.size()));
00104 }
00105 }
00106
00107
00108 void LogDatabaseProxyModel::setColorizeLogs(bool colorize_logs)
00109 {
00110 if (colorize_logs == colorize_logs_) {
00111 return;
00112 }
00113
00114 colorize_logs_ = colorize_logs;
00115 QSettings settings;
00116 settings.setValue(SettingsKeys::COLORIZE_LOGS, colorize_logs_);
00117
00118 if (msg_mapping_.size()) {
00119 Q_EMIT dataChanged(index(0), index(msg_mapping_.size()));
00120 }
00121 }
00122
00123 void LogDatabaseProxyModel::setDisplayTime(bool display)
00124 {
00125 if (display == display_time_) {
00126 return;
00127 }
00128
00129 display_time_ = display;
00130
00131 QSettings settings;
00132 settings.setValue(SettingsKeys::DISPLAY_TIMESTAMPS, display_time_);
00133
00134 if (msg_mapping_.size()) {
00135 Q_EMIT dataChanged(index(0), index(msg_mapping_.size()));
00136 }
00137 }
00138
00139 void LogDatabaseProxyModel::setUseRegularExpressions(bool useRegexps)
00140 {
00141 if (useRegexps == use_regular_expressions_) {
00142 return;
00143 }
00144
00145 use_regular_expressions_ = useRegexps;
00146 QSettings settings;
00147 settings.setValue(SettingsKeys::USE_REGEXPS, useRegexps);
00148 reset();
00149 }
00150
00151 void LogDatabaseProxyModel::setIncludeFilters(
00152 const QStringList &list)
00153 {
00154 include_strings_ = list;
00155
00156
00157 reset();
00158 }
00159
00160 void LogDatabaseProxyModel::setExcludeFilters(
00161 const QStringList &list)
00162 {
00163 exclude_strings_ = list;
00164
00165
00166 reset();
00167 }
00168
00169
00170 void LogDatabaseProxyModel::setIncludeRegexpPattern(const QString& pattern)
00171 {
00172 include_regexp_.setPattern(pattern);
00173 QSettings settings;
00174 settings.setValue(SettingsKeys::INCLUDE_FILTER, pattern);
00175 reset();
00176 }
00177
00178 void LogDatabaseProxyModel::setExcludeRegexpPattern(const QString& pattern)
00179 {
00180 exclude_regexp_.setPattern(pattern);
00181 QSettings settings;
00182 settings.setValue(SettingsKeys::EXCLUDE_FILTER, pattern);
00183 reset();
00184 }
00185
00186 void LogDatabaseProxyModel::setDebugColor(const QColor& debug_color)
00187 {
00188 debug_color_ = debug_color;
00189 QSettings settings;
00190 settings.setValue(SettingsKeys::DEBUG_COLOR, debug_color);
00191 reset();
00192 }
00193
00194 void LogDatabaseProxyModel::setInfoColor(const QColor& info_color)
00195 {
00196 info_color_ = info_color;
00197 QSettings settings;
00198 settings.setValue(SettingsKeys::INFO_COLOR, info_color);
00199 reset();
00200 }
00201
00202 void LogDatabaseProxyModel::setWarnColor(const QColor& warn_color)
00203 {
00204 warn_color_ = warn_color;
00205 QSettings settings;
00206 settings.setValue(SettingsKeys::WARN_COLOR, warn_color);
00207 reset();
00208 }
00209
00210 void LogDatabaseProxyModel::setErrorColor(const QColor& error_color)
00211 {
00212 error_color_ = error_color;
00213 QSettings settings;
00214 settings.setValue(SettingsKeys::ERROR_COLOR, error_color);
00215 reset();
00216 }
00217
00218 void LogDatabaseProxyModel::setFatalColor(const QColor& fatal_color)
00219 {
00220 fatal_color_ = fatal_color;
00221 QSettings settings;
00222 settings.setValue(SettingsKeys::FATAL_COLOR, fatal_color);
00223 reset();
00224 }
00225
00226 int LogDatabaseProxyModel::rowCount(const QModelIndex &parent) const
00227 {
00228 if (parent.isValid()) {
00229 return 0;
00230 }
00231
00232 return msg_mapping_.size();
00233 }
00234
00235
00236 bool LogDatabaseProxyModel::isIncludeValid() const
00237 {
00238 if (use_regular_expressions_ && !include_regexp_.isValid()) {
00239 return false;
00240 }
00241 return true;
00242 }
00243
00244 bool LogDatabaseProxyModel::isExcludeValid() const
00245 {
00246 if (use_regular_expressions_ && !exclude_regexp_.isValid()) {
00247 return false;
00248 }
00249 return true;
00250 }
00251
00252
00253
00254
00255
00256 int LogDatabaseProxyModel::getItemIndex(const QString searchText, int index, int increment)
00257 {
00258 int searchNotFound = -1;
00259 int counter=0;
00260 bool partialSearch = false;
00261 if(searchText==""||msg_mapping_.size()==0)
00262 {
00263 clearSearchFailure();
00264 return searchNotFound;
00265 }
00266
00267
00268 if(index<0)
00269 {
00270 index = msg_mapping_.size()-1;
00271 }
00272 else if(index>=msg_mapping_.size())
00273 {
00274 index = 0;
00275 }
00276
00277
00278
00279
00280
00281
00282
00283 if(searchText.contains(failedSearchText_) && failedSearchText_ != "" && failedSearchIndex_ !=0 && failedSearchIndex_ <= msg_mapping_.size() )
00284 {
00285 partialSearch = true;
00286 index = failedSearchIndex_-1;
00287 counter = failedSearchIndex_;
00288 }
00289 int i;
00290 for(i=0; i<msg_mapping_.size();i++)
00291 {
00292 const LineMap line_idx = msg_mapping_[index];
00293 const LogEntry &item = db_->log()[line_idx.log_index];
00294 QString tempString = item.text.join("|");
00295 if(tempString.toUpper().contains(searchText))
00296 {
00297 clearSearchFailure();
00298 return index;
00299 }
00300 counter++;
00301 if(counter>=msg_mapping_.size())
00302 {
00303 if((!partialSearch)||(failedSearchText_ == ""))
00304 {
00305 failedSearchText_ = searchText;
00306 }
00307 failedSearchIndex_ = msg_mapping_.size();
00308 return searchNotFound;
00309 }
00310
00311 index = index + increment;
00312 if(index<0)
00313 {
00314 index = msg_mapping_.size()-1;
00315 }
00316 else if(index>=msg_mapping_.size())
00317 {
00318 index = 0;
00319 }
00320 }
00321
00322 return -1;
00323 }
00324
00325 void LogDatabaseProxyModel::clearSearchFailure()
00326 {
00327
00328 failedSearchIndex_ = 0;
00329 failedSearchText_ = "";
00330 }
00331
00332 QVariant LogDatabaseProxyModel::data(
00333 const QModelIndex &index, int role) const
00334 {
00335 switch (role)
00336 {
00337
00338
00339 case Qt::DisplayRole:
00340 case Qt::ToolTipRole:
00341 case ExtendedLogRole:
00342 break;
00343 case Qt::ForegroundRole:
00344 if (colorize_logs_) {
00345 break;
00346 }
00347 default:
00348 return QVariant();
00349 }
00350
00351 if (index.parent().isValid() &&
00352 static_cast<size_t>(index.row()) >= msg_mapping_.size()) {
00353 return QVariant();
00354 }
00355
00356 const LineMap line_idx = msg_mapping_[index.row()];
00357 const LogEntry &item = db_->log()[line_idx.log_index];
00358
00359 if (role == Qt::DisplayRole) {
00360 char level = '?';
00361 if (item.level == rosgraph_msgs::Log::DEBUG) {
00362 level = 'D';
00363 } else if (item.level == rosgraph_msgs::Log::INFO) {
00364 level = 'I';
00365 } else if (item.level == rosgraph_msgs::Log::WARN) {
00366 level = 'W';
00367 } else if (item.level == rosgraph_msgs::Log::ERROR) {
00368 level = 'E';
00369 } else if (item.level == rosgraph_msgs::Log::FATAL) {
00370 level = 'F';
00371 }
00372
00373 char stamp[128];
00374 if (display_absolute_time_) {
00375 snprintf(stamp, sizeof(stamp),
00376 "%u.%09u",
00377 item.stamp.sec,
00378 item.stamp.nsec);
00379 } else {
00380 ros::Duration t = item.stamp - db_->minTime();
00381
00382 int32_t secs = t.sec;
00383 int hours = secs / 60 / 60;
00384 int minutes = (secs / 60) % 60;
00385 int seconds = (secs % 60);
00386 int milliseconds = t.nsec / 1000000;
00387
00388 snprintf(stamp, sizeof(stamp),
00389 "%d:%02d:%02d:%03d",
00390 hours, minutes, seconds, milliseconds);
00391 }
00392
00393 char header[1024];
00394 if (display_time_) {
00395 snprintf(header, sizeof(header),
00396 "[%c %s] ", level, stamp);
00397 } else {
00398 snprintf(header, sizeof(header),
00399 "[%c] ", level);
00400 }
00401
00402
00403
00404
00405
00406 if (line_idx.line_index != 0) {
00407 size_t len = strnlen(header, sizeof(header));
00408 for (size_t i = 0; i < len; i++) {
00409 header[i] = ' ';
00410 }
00411 }
00412
00413 return QVariant(QString(header) + item.text[line_idx.line_index]);
00414 }
00415 else if (role == Qt::ForegroundRole && colorize_logs_) {
00416 switch (item.level) {
00417 case rosgraph_msgs::Log::DEBUG:
00418 return QVariant(debug_color_);
00419 case rosgraph_msgs::Log::INFO:
00420 return QVariant(info_color_);
00421 case rosgraph_msgs::Log::WARN:
00422 return QVariant(warn_color_);
00423 case rosgraph_msgs::Log::ERROR:
00424 return QVariant(error_color_);
00425 case rosgraph_msgs::Log::FATAL:
00426 return QVariant(fatal_color_);
00427 default:
00428 return QVariant(info_color_);
00429 }
00430 }
00431 else if (role == Qt::ToolTipRole) {
00432 char buffer[4096];
00433 snprintf(buffer, sizeof(buffer),
00434 "<p style='white-space:pre'>"
00435 "Timestamp: %d.%09d\n"
00436 "Seq: %d\n"
00437 "Node: %s\n"
00438 "Function: %s\n"
00439 "File: %s\n"
00440 "Line: %d\n"
00441 "\n",
00442 item.stamp.sec,
00443 item.stamp.nsec,
00444 item.seq,
00445 item.node.c_str(),
00446 item.function.c_str(),
00447 item.file.c_str(),
00448 item.line);
00449
00450 QString text = (QString(buffer) +
00451 item.text.join("\n") +
00452 QString("</p>"));
00453
00454 return QVariant(text);
00455 } else if (role == LogDatabaseProxyModel::ExtendedLogRole) {
00456 char buffer[4096];
00457 snprintf(buffer, sizeof(buffer),
00458 "Timestamp: %d.%09d\n"
00459 "Node: %s\n"
00460 "Function: %s\n"
00461 "File: %s\n"
00462 "Line: %d\n"
00463 "Message: ",
00464 item.stamp.sec,
00465 item.stamp.nsec,
00466 item.node.c_str(),
00467 item.function.c_str(),
00468 item.file.c_str(),
00469 item.line);
00470
00471 QString text = (QString(buffer) +
00472 item.text.join("\n"));
00473
00474 return QVariant(text);
00475 }
00476
00477 return QVariant();
00478 }
00479
00480 void LogDatabaseProxyModel::reset()
00481 {
00482 beginResetModel();
00483 msg_mapping_.clear();
00484 early_mapping_.clear();
00485 earliest_log_index_ = db_->log().size();
00486 latest_log_index_ = earliest_log_index_;
00487 endResetModel();
00488 scheduleIdleProcessing();
00489 }
00490
00491
00492 void LogDatabaseProxyModel::saveToFile(const QString& filename) const
00493 {
00494 if (filename.endsWith(".bag", Qt::CaseInsensitive)) {
00495 saveBagFile(filename);
00496 }
00497 else {
00498 saveTextFile(filename);
00499 }
00500 }
00501
00502 void LogDatabaseProxyModel::saveBagFile(const QString& filename) const
00503 {
00504 rosbag::Bag bag(filename.toStdString().c_str(), rosbag::bagmode::Write);
00505
00506 size_t idx = 0;
00507 while (idx < msg_mapping_.size()) {
00508 const LineMap line_map = msg_mapping_[idx];
00509 const LogEntry &item = db_->log()[line_map.log_index];
00510
00511 rosgraph_msgs::Log log;
00512 log.file = item.file;
00513 log.function = item.function;
00514 log.header.seq = item.seq;
00515 if (item.stamp < ros::TIME_MIN) {
00516
00517
00518
00519 log.header.stamp = ros::Time::now();
00520 qWarning("Msg with seq %d had time (%d); it's less than ros::TIME_MIN, which is invalid. "
00521 "Writing 'now' instead.",
00522 log.header.seq, item.stamp.sec);
00523 } else {
00524 log.header.stamp = item.stamp;
00525 }
00526 log.level = item.level;
00527 log.line = item.line;
00528 log.msg = item.text.join("\n").toStdString();
00529 log.name = item.node;
00530 bag.write("/rosout", log.header.stamp, log);
00531
00532
00533 idx++;
00534 while (idx < msg_mapping_.size() && msg_mapping_[idx].log_index == line_map.log_index) {
00535 idx++;
00536 }
00537 }
00538 bag.close();
00539 }
00540
00541 void LogDatabaseProxyModel::saveTextFile(const QString& filename) const
00542 {
00543 QFile outFile(filename);
00544 outFile.open(QFile::WriteOnly);
00545 QTextStream outstream(&outFile);
00546 for(size_t i = 0; i < msg_mapping_.size(); i++)
00547 {
00548 QString line = data(index(i), Qt::DisplayRole).toString();
00549 outstream << line << '\n';
00550 }
00551 outstream.flush();
00552 outFile.close();
00553 }
00554
00555 void LogDatabaseProxyModel::handleDatabaseCleared()
00556 {
00557 reset();
00558 clearSearchFailure();
00559 }
00560
00561 void LogDatabaseProxyModel::processNewMessages()
00562 {
00563 std::deque<LineMap> new_items;
00564
00565
00566
00567 for (;
00568 latest_log_index_ < db_->log().size();
00569 latest_log_index_++)
00570 {
00571 const LogEntry &item = db_->log()[latest_log_index_];
00572 if (!acceptLogEntry(item)) {
00573 continue;
00574 }
00575
00576 for (int i = 0; i < item.text.size(); i++) {
00577 new_items.push_back(LineMap(latest_log_index_, i));
00578 }
00579 }
00580
00581 if (!new_items.empty()) {
00582 beginInsertRows(QModelIndex(),
00583 msg_mapping_.size(),
00584 msg_mapping_.size() + new_items.size() - 1);
00585 msg_mapping_.insert(msg_mapping_.end(),
00586 new_items.begin(),
00587 new_items.end());
00588 endInsertRows();
00589
00590 Q_EMIT messagesAdded();
00591 }
00592 }
00593
00594 void LogDatabaseProxyModel::processOldMessages()
00595 {
00596
00597
00598
00599
00600
00601
00602
00603
00604 for (size_t i = 0;
00605 earliest_log_index_ != 0 && i < 100;
00606 earliest_log_index_--, i++)
00607 {
00608 const LogEntry &item = db_->log()[earliest_log_index_-1];
00609 if (!acceptLogEntry(item)) {
00610 continue;
00611 }
00612
00613 for (int i = 0; i < item.text.size(); i++) {
00614
00615 early_mapping_.push_front(
00616 LineMap(earliest_log_index_-1, item.text.size()-1-i));
00617 }
00618 }
00619
00620 if ((earliest_log_index_ == 0 && early_mapping_.size()) ||
00621 (early_mapping_.size() > 200)) {
00622 beginInsertRows(QModelIndex(),
00623 0,
00624 early_mapping_.size() - 1);
00625 msg_mapping_.insert(msg_mapping_.begin(),
00626 early_mapping_.begin(),
00627 early_mapping_.end());
00628 early_mapping_.clear();
00629 endInsertRows();
00630
00631 Q_EMIT messagesAdded();
00632 }
00633
00634 scheduleIdleProcessing();
00635 }
00636
00637 void LogDatabaseProxyModel::scheduleIdleProcessing()
00638 {
00639
00640
00641 if (earliest_log_index_ > 0) {
00642 QTimer::singleShot(0, this, SLOT(processOldMessages()));
00643 }
00644 }
00645
00646 bool LogDatabaseProxyModel::acceptLogEntry(const LogEntry &item)
00647 {
00648 if (!(item.level & severity_mask_)) {
00649 return false;
00650 }
00651
00652 if (names_.count(item.node) == 0) {
00653 return false;
00654 }
00655
00656 if (!testIncludeFilter(item)) {
00657 return false;
00658 }
00659
00660 if (use_regular_expressions_) {
00661
00662
00663
00664
00665
00666 return exclude_regexp_.isEmpty() || exclude_regexp_.indexIn(item.text.join(" ")) < 0;
00667 } else {
00668 for (int i = 0; i < exclude_strings_.size(); i++) {
00669 if (item.text.join(" ").contains(exclude_strings_[i], Qt::CaseInsensitive)) {
00670 return false;
00671 }
00672 }
00673 }
00674
00675 return true;
00676 }
00677
00678
00679
00680
00681 bool LogDatabaseProxyModel::testIncludeFilter(const LogEntry &item)
00682 {
00683 if (use_regular_expressions_) {
00684 return include_regexp_.indexIn(item.text.join(" ")) >= 0;
00685 } else {
00686 if (include_strings_.empty()) {
00687 return true;
00688 }
00689
00690 for (int i = 0; i < include_strings_.size(); i++) {
00691 if (item.text.join(" ").contains(include_strings_[i], Qt::CaseInsensitive)) {
00692 return true;
00693 }
00694 }
00695 }
00696
00697 return false;
00698 }
00699
00700 void LogDatabaseProxyModel::minTimeUpdated()
00701 {
00702 if (display_time_ &&
00703 !display_absolute_time_
00704 && msg_mapping_.size()) {
00705 Q_EMIT dataChanged(index(0), index(msg_mapping_.size()));
00706 }
00707 }
00708 }