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 <stdint.h>
00032 #include <stdio.h>
00033 #include <set>
00034
00035 #include <rosgraph_msgs/Log.h>
00036 #include <ros/master.h>
00037
00038
00039 #include <swri_console/console_window.h>
00040 #include <swri_console/log_database.h>
00041 #include <swri_console/log_database_proxy_model.h>
00042 #include <swri_console/node_list_model.h>
00043 #include <swri_console/settings_keys.h>
00044
00045 #include <QColorDialog>
00046 #include <QRegExp>
00047 #include <QApplication>
00048 #include <QClipboard>
00049 #include <QDateTime>
00050 #include <QFileDialog>
00051 #include <QDir>
00052 #include <QScrollBar>
00053 #include <QMenu>
00054 #include <QSettings>
00055
00056 using namespace Qt;
00057
00058 namespace swri_console {
00059
00060 ConsoleWindow::ConsoleWindow(LogDatabase *db)
00061 :
00062 QMainWindow(),
00063 db_(db),
00064 db_proxy_(new LogDatabaseProxyModel(db)),
00065 node_list_model_(new NodeListModel(db))
00066 {
00067 ui.setupUi(this);
00068
00069 QObject::connect(ui.action_NewWindow, SIGNAL(triggered(bool)),
00070 this, SIGNAL(createNewWindow()));
00071
00072 QObject::connect(ui.action_Copy, SIGNAL(triggered()),
00073 this, SLOT(copyLogs()));
00074
00075 QObject::connect(ui.action_CopyExtended, SIGNAL(triggered()),
00076 this, SLOT(copyExtendedLogs()));
00077
00078 QObject::connect(ui.action_SelectAll, SIGNAL(triggered()),
00079 this, SLOT(selectAllLogs()));
00080
00081 QObject::connect(ui.action_ReadBagFile, SIGNAL(triggered(bool)),
00082 this, SIGNAL(readBagFile()));
00083
00084 QObject::connect(ui.action_SaveLogs, SIGNAL(triggered(bool)),
00085 this, SLOT(saveLogs()));
00086
00087 QObject::connect(ui.action_AbsoluteTimestamps, SIGNAL(toggled(bool)),
00088 db_proxy_, SLOT(setAbsoluteTime(bool)));
00089
00090 QObject::connect(ui.action_ShowTimestamps, SIGNAL(toggled(bool)),
00091 db_proxy_, SLOT(setDisplayTime(bool)));
00092
00093 QObject::connect(ui.action_RegularExpressions, SIGNAL(toggled(bool)),
00094 db_proxy_, SLOT(setUseRegularExpressions(bool)));
00095
00096 QObject::connect(ui.action_RegularExpressions, SIGNAL(toggled(bool)),
00097 this, SLOT(updateIncludeLabel()));
00098
00099 QObject::connect(ui.action_RegularExpressions, SIGNAL(toggled(bool)),
00100 this, SLOT(updateExcludeLabel()));
00101
00102 QObject::connect(ui.action_SelectFont, SIGNAL(triggered(bool)),
00103 this, SIGNAL(selectFont()));
00104
00105 QObject::connect(ui.action_ColorizeLogs, SIGNAL(toggled(bool)),
00106 db_proxy_, SLOT(setColorizeLogs(bool)));
00107
00108 QObject::connect(ui.debugColorWidget, SIGNAL(clicked(bool)),
00109 this, SLOT(setDebugColor()));
00110 QObject::connect(ui.infoColorWidget, SIGNAL(clicked(bool)),
00111 this, SLOT(setInfoColor()));
00112 QObject::connect(ui.warnColorWidget, SIGNAL(clicked(bool)),
00113 this, SLOT(setWarnColor()));
00114 QObject::connect(ui.errorColorWidget, SIGNAL(clicked(bool)),
00115 this, SLOT(setErrorColor()));
00116 QObject::connect(ui.fatalColorWidget, SIGNAL(clicked(bool)),
00117 this, SLOT(setFatalColor()));
00118
00119 ui.nodeList->setModel(node_list_model_);
00120 ui.messageList->setModel(db_proxy_);
00121 ui.messageList->setUniformItemSizes(true);
00122
00123 QObject::connect(
00124 ui.nodeList->selectionModel(),
00125 SIGNAL(selectionChanged(const QItemSelection &,
00126 const QItemSelection &)),
00127 this,
00128 SLOT(nodeSelectionChanged()));
00129
00130 QObject::connect(
00131 ui.checkDebug, SIGNAL(toggled(bool)),
00132 this, SLOT(setSeverityFilter()));
00133 QObject::connect(
00134 ui.checkInfo, SIGNAL(toggled(bool)),
00135 this, SLOT(setSeverityFilter()));
00136 QObject::connect(
00137 ui.checkWarn, SIGNAL(toggled(bool)),
00138 this, SLOT(setSeverityFilter()));
00139 QObject::connect(
00140 ui.checkError, SIGNAL(toggled(bool)),
00141 this, SLOT(setSeverityFilter()));
00142 QObject::connect(
00143 ui.checkFatal, SIGNAL(toggled(bool)),
00144 this, SLOT(setSeverityFilter()));
00145 QObject::connect(
00146 db_proxy_, SIGNAL(messagesAdded()),
00147 this, SLOT(messagesAdded()));
00148 QObject::connect(ui.checkFollowNewest, SIGNAL(toggled(bool)),
00149 this, SLOT(setFollowNewest(bool)));
00150
00151
00152 QObject::connect(ui.messageList, SIGNAL(customContextMenuRequested(const QPoint&)),
00153 this, SLOT(showLogContextMenu(const QPoint&)));
00154
00155 QObject::connect(ui.clearAllButton, SIGNAL(clicked()),
00156 this, SLOT(clearAll()));
00157 QObject::connect(ui.clearMessagesButton, SIGNAL(clicked()),
00158 this, SLOT(clearMessages()));
00159
00160 QObject::connect(
00161 ui.messageList->verticalScrollBar(), SIGNAL(valueChanged(int)),
00162 this, SLOT(userScrolled(int)));
00163
00164 QObject::connect(
00165 ui.includeText, SIGNAL(textChanged(const QString &)),
00166 this, SLOT(includeFilterUpdated(const QString &)));
00167
00168 QObject::connect(
00169 ui.excludeText, SIGNAL(textChanged(const QString &)),
00170 this, SLOT(excludeFilterUpdated(const QString &)));
00171
00172
00173 QObject::connect(
00174 ui.searchText, SIGNAL(textChanged(const QString &)),
00175 this, SLOT(searchIndex()));
00176
00177 QObject::connect(ui.pushPrev, SIGNAL(clicked()),
00178 this, SLOT(prevIndex()));
00179
00180 QObject::connect(ui.pushNext, SIGNAL(clicked()),
00181 this, SLOT(nextIndex()));
00182
00183
00184 QList<int> sizes;
00185 sizes.append(100);
00186 sizes.append(1000);
00187 ui.splitter->setSizes(sizes);
00188
00189 loadSettings();
00190 }
00191
00192 ConsoleWindow::~ConsoleWindow()
00193 {
00194 delete db_proxy_;
00195 }
00196
00197 void ConsoleWindow::clearAll()
00198 {
00199 db_->clear();
00200 node_list_model_->clear();
00201 db_proxy_->clearSearchFailure();
00202 }
00203
00204 void ConsoleWindow::clearMessages()
00205 {
00206 db_->clear();
00207 db_proxy_->clearSearchFailure();
00208 }
00209
00210 void ConsoleWindow::saveLogs()
00211 {
00212 QString defaultname = QDateTime::currentDateTime().toString(Qt::ISODate) + ".bag";
00213 QString filename = QFileDialog::getSaveFileName(this,
00214 "Save Logs",
00215 QDir::homePath() + QDir::separator() + defaultname,
00216 tr("Bag Files (*.bag);;Text Files (*.txt)"));
00217 if (filename != NULL && !filename.isEmpty()) {
00218 db_proxy_->saveToFile(filename);
00219 }
00220 }
00221
00222 void ConsoleWindow::connected(bool connected)
00223 {
00224 if (connected) {
00225
00226 QString currentUrl = QString::fromStdString(ros::master::getURI());
00227 statusBar()->showMessage("Connected to ROS Master. URL: "+currentUrl);
00228 } else {
00229 statusBar()->showMessage("Disconnected from ROS Master.");
00230 }
00231 }
00232
00233 void ConsoleWindow::closeEvent(QCloseEvent *event)
00234 {
00235 QMainWindow::closeEvent(event);
00236 }
00237
00238 void ConsoleWindow::nodeSelectionChanged()
00239 {
00240 db_proxy_->clearSearchFailure();
00241 QModelIndexList selection = ui.nodeList->selectionModel()->selectedIndexes();
00242 std::set<std::string> nodes;
00243 QStringList node_names;
00244
00245 for (int i = 0; i < selection.size(); i++) {
00246 std::string name = node_list_model_->nodeName(selection[i]);
00247 nodes.insert(name);
00248 node_names.append(name.c_str());
00249 }
00250
00251 db_proxy_->setNodeFilter(nodes);
00252
00253 for (int i = 0; i < node_names.size(); i++) {
00254 node_names[i] = node_names[i].split("/", QString::SkipEmptyParts).last();
00255 }
00256
00257 setWindowTitle(QString("SWRI Console (") + node_names.join(", ") + ")");
00258 }
00259
00260 void ConsoleWindow::setSeverityFilter()
00261 {
00262 uint8_t mask = 0;
00263
00264 if (ui.checkDebug->isChecked()) {
00265 mask |= rosgraph_msgs::Log::DEBUG;
00266 }
00267 if (ui.checkInfo->isChecked()) {
00268 mask |= rosgraph_msgs::Log::INFO;
00269 }
00270 if (ui.checkWarn->isChecked()) {
00271 mask |= rosgraph_msgs::Log::WARN;
00272 }
00273 if (ui.checkError->isChecked()) {
00274 mask |= rosgraph_msgs::Log::ERROR;
00275 }
00276 if (ui.checkFatal->isChecked()) {
00277 mask |= rosgraph_msgs::Log::FATAL;
00278 }
00279
00280 QSettings settings;
00281 settings.setValue(SettingsKeys::SHOW_DEBUG, ui.checkDebug->isChecked());
00282 settings.setValue(SettingsKeys::SHOW_INFO, ui.checkInfo->isChecked());
00283 settings.setValue(SettingsKeys::SHOW_WARN, ui.checkWarn->isChecked());
00284 settings.setValue(SettingsKeys::SHOW_ERROR, ui.checkError->isChecked());
00285 settings.setValue(SettingsKeys::SHOW_FATAL, ui.checkFatal->isChecked());
00286
00287 db_proxy_->setSeverityFilter(mask);
00288 db_proxy_->clearSearchFailure();
00289 }
00290
00291 void ConsoleWindow::messagesAdded()
00292 {
00293 if (ui.checkFollowNewest->isChecked()) {
00294 ui.messageList->scrollToBottom();
00295 }
00296 }
00297
00298
00299 void ConsoleWindow::showLogContextMenu(const QPoint& point)
00300 {
00301 QMenu contextMenu(tr("Context menu"), ui.messageList);
00302
00303 QAction select_all(tr("Select All"), ui.messageList);
00304 connect(&select_all, SIGNAL(triggered()), this, SLOT(selectAllLogs()));
00305
00306 QAction copy(tr("Copy"), ui.messageList);
00307 connect(©, SIGNAL(triggered()), this, SLOT(copyLogs()));
00308
00309 QAction copy_extended(tr("Copy Extended"), ui.messageList);
00310 connect(©_extended, SIGNAL(triggered()), this, SLOT(copyExtendedLogs()));
00311
00312 QAction alternate_row_colors(tr("Alternate Row Colors"), ui.messageList);
00313 alternate_row_colors.setCheckable(true);
00314 alternate_row_colors.setChecked(ui.messageList->alternatingRowColors());
00315 connect(&alternate_row_colors, SIGNAL(toggled(bool)),
00316 this, SLOT(toggleAlternateRowColors(bool)));
00317
00318 contextMenu.addAction(&select_all);
00319 contextMenu.addAction(©);
00320 contextMenu.addAction(©_extended);
00321 contextMenu.addAction(&alternate_row_colors);
00322
00323 contextMenu.exec(ui.messageList->mapToGlobal(point));
00324 }
00325
00326 void ConsoleWindow::userScrolled(int value)
00327 {
00328 if (value != ui.messageList->verticalScrollBar()->maximum()) {
00329 ui.checkFollowNewest->setChecked(false);
00330 } else {
00331 ui.checkFollowNewest->setChecked(true);
00332 }
00333 }
00334
00335
00336 void ConsoleWindow::selectAllLogs()
00337 {
00338 if (ui.nodeList->hasFocus()) {
00339 ui.nodeList->selectAll();
00340 } else {
00341 ui.messageList->selectAll();
00342 }
00343 }
00344
00345 void ConsoleWindow::copyLogs()
00346 {
00347 QStringList buffer;
00348 foreach(const QModelIndex &index, ui.messageList->selectionModel()->selectedIndexes())
00349 {
00350 buffer << db_proxy_->data(index, Qt::DisplayRole).toString();
00351 }
00352 QApplication::clipboard()->setText(buffer.join(tr("\n")));
00353 }
00354
00355 void ConsoleWindow::copyExtendedLogs()
00356 {
00357 QStringList buffer;
00358 foreach(const QModelIndex &index, ui.messageList->selectionModel()->selectedIndexes())
00359 {
00360 buffer << db_proxy_->data(index, LogDatabaseProxyModel::ExtendedLogRole).toString();
00361 }
00362 QApplication::clipboard()->setText(buffer.join(tr("\n\n")));
00363 }
00364
00365 void ConsoleWindow::setFollowNewest(bool follow)
00366 {
00367 QSettings settings;
00368 settings.setValue(SettingsKeys::FOLLOW_NEWEST, follow);
00369 }
00370
00371 void ConsoleWindow::includeFilterUpdated(const QString &text)
00372 {
00373 QStringList items = text.split(";", QString::SkipEmptyParts);
00374 QStringList filtered;
00375
00376 for (int i = 0; i < items.size(); i++) {
00377 QString x = items[i].trimmed();
00378 if (!x.isEmpty()) {
00379 filtered.append(x);
00380 }
00381 }
00382
00383 db_proxy_->setIncludeFilters(filtered);
00384 db_proxy_->setIncludeRegexpPattern(text);
00385 db_proxy_->clearSearchFailure();
00386 updateIncludeLabel();
00387 }
00388
00389 void ConsoleWindow::excludeFilterUpdated(const QString &text)
00390 {
00391 QStringList items = text.split(";", QString::SkipEmptyParts);
00392 QStringList filtered;
00393
00394 for (int i = 0; i < items.size(); i++) {
00395 QString x = items[i].trimmed();
00396 if (!x.isEmpty()) {
00397 filtered.append(x);
00398 }
00399 }
00400
00401 db_proxy_->setExcludeFilters(filtered);
00402 db_proxy_->setExcludeRegexpPattern(text);
00403 db_proxy_->clearSearchFailure();
00404 updateExcludeLabel();
00405 }
00406
00407
00408 void ConsoleWindow::searchIndex()
00409 {
00410 updateCurrentIndex(SEARCH);
00411 }
00412
00413 void ConsoleWindow::prevIndex()
00414 {
00415 updateCurrentIndex(PREV);
00416 }
00417
00418 void ConsoleWindow::nextIndex()
00419 {
00420 updateCurrentIndex(NEXT);
00421 }
00422
00423
00424
00425
00426
00427
00428 void ConsoleWindow::updateCurrentIndex(function sF)
00429 {
00430 int rowSearchStart = ui.messageList->currentIndex().row();
00431 int increment = 1;
00432 QString searchText = ui.searchText->text();
00433 searchText = searchText.toUpper().trimmed();
00434
00435 if(sF == NEXT){
00436 rowSearchStart++;
00437 }
00438
00439 else if(sF== PREV){
00440 rowSearchStart--;
00441 increment=-1;
00442 }
00443
00444 else if(sF==SEARCH )
00445 {
00446 if (rowSearchStart==-1){
00447 rowSearchStart =0;
00448 }
00449 }
00450 else
00451 {
00452
00453 printf("Invalid string passed to ConsoleWindow::nextIndex");
00454 return;
00455 }
00456
00457 int newRowIndex = db_proxy_->getItemIndex(searchText,rowSearchStart, increment);
00458 ui.messageList->clearSelection();
00459 if(newRowIndex == -1)
00460 {
00461 return;
00462 }
00463
00464 QModelIndex index = ui.messageList->model()->index(newRowIndex,0);
00465 ui.messageList->setCurrentIndex(index);
00466 ui.checkFollowNewest->setChecked(false);
00467
00468
00469 }
00470
00471
00472 void ConsoleWindow::updateIncludeLabel()
00473 {
00474 if (db_proxy_->isIncludeValid()) {
00475 ui.includeLabel->setText("Include");
00476 } else {
00477 ui.includeLabel->setText("<font color='red'>Include</font>");
00478 }
00479 }
00480
00481 void ConsoleWindow::updateExcludeLabel()
00482 {
00483 if (db_proxy_->isExcludeValid()) {
00484 ui.excludeLabel->setText("Exclude");
00485 } else {
00486 ui.excludeLabel->setText("<font color='red'>Exclude</font>");
00487 }
00488 }
00489
00490 void ConsoleWindow::setFont(const QFont &font)
00491 {
00492 ui.messageList->setFont(font);
00493 ui.nodeList->setFont(font);
00494 }
00495
00496 void ConsoleWindow::setDebugColor()
00497 {
00498 chooseButtonColor(ui.debugColorWidget);
00499 }
00500
00501 void ConsoleWindow::setInfoColor()
00502 {
00503 chooseButtonColor(ui.infoColorWidget);
00504 }
00505
00506 void ConsoleWindow::setWarnColor()
00507 {
00508 chooseButtonColor(ui.warnColorWidget);
00509 }
00510
00511 void ConsoleWindow::setErrorColor()
00512 {
00513 chooseButtonColor(ui.errorColorWidget);
00514 }
00515
00516 void ConsoleWindow::setFatalColor()
00517 {
00518 chooseButtonColor(ui.fatalColorWidget);
00519 }
00520
00521 void ConsoleWindow::chooseButtonColor(QPushButton* widget)
00522 {
00523 QColor old_color = getButtonColor(widget);
00524 QColor color = QColorDialog::getColor(old_color, this);
00525 if (color.isValid()) {
00526 updateButtonColor(widget, color);
00527 }
00528 }
00529
00530 QColor ConsoleWindow::getButtonColor(const QPushButton* button) const
00531 {
00532 QString ss = button->styleSheet();
00533 QRegExp re("background: (#\\w*);");
00534 QColor old_color;
00535 if (re.indexIn(ss) >= 0) {
00536 old_color = QColor(re.cap(1));
00537 }
00538 return old_color;
00539 }
00540
00541 void ConsoleWindow::updateButtonColor(QPushButton* widget, const QColor& color)
00542 {
00543 QString s("background: #"
00544 + QString(color.red() < 16? "0" : "") + QString::number(color.red(),16)
00545 + QString(color.green() < 16? "0" : "") + QString::number(color.green(),16)
00546 + QString(color.blue() < 16? "0" : "") + QString::number(color.blue(),16) + ";");
00547 widget->setStyleSheet(s);
00548 widget->update();
00549
00550 if (widget == ui.debugColorWidget) {
00551 db_proxy_->setDebugColor(color);
00552 }
00553 else if (widget == ui.infoColorWidget) {
00554 db_proxy_->setInfoColor(color);
00555 }
00556 else if (widget == ui.warnColorWidget) {
00557 db_proxy_->setWarnColor(color);
00558 }
00559 else if (widget == ui.errorColorWidget) {
00560 db_proxy_->setErrorColor(color);
00561 }
00562 else if (widget == ui.fatalColorWidget) {
00563 db_proxy_->setFatalColor(color);
00564 }
00565 else {
00566 qWarning("Unexpected widget passed to ConsoleWindow::updateButtonColor.");
00567 }
00568 }
00569
00570 void ConsoleWindow::loadColorButtonSetting(const QString& key, QPushButton* button)
00571 {
00572 QSettings settings;
00573 QColor defaultColor;
00574
00575
00576 if (button == ui.debugColorWidget) {
00577 defaultColor = Qt::gray;
00578 }
00579 else if (button == ui.infoColorWidget) {
00580 defaultColor = Qt::black;
00581 }
00582 else if (button == ui.warnColorWidget) {
00583 defaultColor = QColor(255, 127, 0);
00584 }
00585 else if (button == ui.errorColorWidget) {
00586 defaultColor = Qt::red;
00587 }
00588 else if (button == ui.fatalColorWidget) {
00589 defaultColor = Qt::magenta;
00590 }
00591 QColor color = settings.value(key, defaultColor).value<QColor>();
00592 updateButtonColor(button, color);
00593 }
00594
00595 void ConsoleWindow::toggleAlternateRowColors(bool checked)
00596 {
00597 ui.messageList->setAlternatingRowColors(checked);
00598
00599 QSettings settings;
00600 settings.setValue(SettingsKeys::ALTERNATE_LOG_ROW_COLORS, checked);
00601 }
00602
00603 void ConsoleWindow::loadSettings()
00604 {
00605
00606 loadBooleanSetting(SettingsKeys::DISPLAY_TIMESTAMPS, ui.action_ShowTimestamps);
00607 loadBooleanSetting(SettingsKeys::ABSOLUTE_TIMESTAMPS, ui.action_AbsoluteTimestamps);
00608 loadBooleanSetting(SettingsKeys::USE_REGEXPS, ui.action_RegularExpressions);
00609 loadBooleanSetting(SettingsKeys::COLORIZE_LOGS, ui.action_ColorizeLogs);
00610 loadBooleanSetting(SettingsKeys::FOLLOW_NEWEST, ui.checkFollowNewest);
00611
00612
00613
00614
00615 QSettings settings;
00616 bool showDebug = settings.value(SettingsKeys::SHOW_DEBUG, true).toBool();
00617 bool showInfo = settings.value(SettingsKeys::SHOW_INFO, true).toBool();
00618 bool showWarn = settings.value(SettingsKeys::SHOW_WARN, true).toBool();
00619 bool showError = settings.value(SettingsKeys::SHOW_ERROR, true).toBool();
00620 bool showFatal = settings.value(SettingsKeys::SHOW_FATAL, true).toBool();
00621 ui.checkDebug->setChecked(showDebug);
00622 ui.checkInfo->setChecked(showInfo);
00623 ui.checkWarn->setChecked(showWarn);
00624 ui.checkError->setChecked(showError);
00625 ui.checkFatal->setChecked(showFatal);
00626 setSeverityFilter();
00627
00628
00629 loadColorButtonSetting(SettingsKeys::DEBUG_COLOR, ui.debugColorWidget);
00630 loadColorButtonSetting(SettingsKeys::INFO_COLOR, ui.infoColorWidget);
00631 loadColorButtonSetting(SettingsKeys::WARN_COLOR, ui.warnColorWidget);
00632 loadColorButtonSetting(SettingsKeys::ERROR_COLOR, ui.errorColorWidget);
00633 loadColorButtonSetting(SettingsKeys::FATAL_COLOR, ui.fatalColorWidget);
00634
00635
00636 QString includeFilter = settings.value(SettingsKeys::INCLUDE_FILTER, "").toString();
00637 ui.includeText->setText(includeFilter);
00638 QString excludeFilter = settings.value(SettingsKeys::EXCLUDE_FILTER, "").toString();
00639 ui.excludeText->setText(excludeFilter);
00640
00641 bool alternate_row_colors = settings.value(SettingsKeys::ALTERNATE_LOG_ROW_COLORS, true).toBool();
00642 ui.messageList->setAlternatingRowColors(alternate_row_colors);
00643 }
00644 }
00645