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