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