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 }