logview.cpp

00001
00002 /***************************************************************************
00003  *  logview.cpp - Fawkes log view widget
00004  *
00005  *  Created: Mon Nov 02 13:19:03 2008
00006  *  Copyright  2008  Tim Niemueller [www.niemueller.de]
00007  *
00008  ****************************************************************************/
00009
00010 /*  This program is free software; you can redistribute it and/or modify
00011  *  it under the terms of the GNU General Public License as published by
00012  *  the Free Software Foundation; either version 2 of the License, or
00013  *  (at your option) any later version. A runtime exception applies to
00014  *  this software (see LICENSE.GPL_WRE file mentioned below for details).
00015  *
00016  *  This program is distributed in the hope that it will be useful,
00017  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00018  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00019  *  GNU Library General Public License for more details.
00020  *
00021  *  Read the full text in the LICENSE.GPL_WRE file in the doc directory.
00022  */
00023
00024 #include <gui_utils/logview.h>
00025 #include <gui_utils/connection_dispatcher.h>
00026 #include <netcomm/fawkes/client.h>
00027 #include <netcomm/utils/network_logger.h>
00028
00029 #include <gtkmm.h>
00030
00031 namespace fawkes {
00032 #if 0 /* just to make Emacs auto-indent happy */
00033 }
00034 #endif
00035 
00036 
00037 /** @class LogView <gui_utils/logview.h>
00038  * Log View widget.
00039  * This widget derives a Gtk::TreeView and provides an easy way to show
00040  * log messages in a GUI application.
00041  * @author Tim Niemueller
00042  */
00043
00044 
00045 /** Constructor. */
00046 LogView::LogView()
00047 {
00048   ctor();
00049 }
00050
00051 
00052 /** Constructor.
00053  * @param hostname hostname to set for the FawkesNetworkClient.
00054  * @param port port to set for the FawkesNetworkClient.
00055  */
00056 LogView::LogView(const char *hostname, unsigned short int port)
00057 {
00058   ctor(hostname, port);
00059 }
00060
00061 
00062 /** Constructor.
00063  * Special ctor to be used with Glade's get_widget_derived().
00064  * @param cobject Gtk C object
00065  * @param refxml Glade's XML reference
00066  */
00067 LogView::LogView(BaseObjectType* cobject,
00068                  const Glib::RefPtr<Gnome::Glade::Xml>& refxml)
00069   : Gtk::TreeView(cobject)
00070 {
00071   ctor();
00072 }
00073
00074 
00075 /** Destructor. */
00076 LogView::~LogView()
00077 {
00078   FawkesNetworkClient *c = __connection_dispatcher->get_client();
00079   if (c && c->connected()) {
00080     FawkesNetworkMessage *msg = new FawkesNetworkMessage(FAWKES_CID_NETWORKLOGGER,
00081                                                          NetworkLogger::MSGTYPE_UNSUBSCRIBE);
00082     c->enqueue(msg);
00083     msg->unref();
00084   }
00085   delete __connection_dispatcher;
00086 }
00087
00088 
00089 /** Internal constructor method. */
00090 void
00091 LogView::ctor(const char *hostname, unsigned short int port)
00092 {
00093   __list = Gtk::ListStore::create(__record);
00094   __have_recently_added_path = false;
00095
00096   __list->signal_row_inserted().connect(sigc::mem_fun(*this, &LogView::on_row_inserted));
00097   set_model(__list);
00098   get_selection()->set_mode(Gtk::SELECTION_NONE);
00099
00100   if ( (hostname != NULL) && (port != 0) ) {
00101     __connection_dispatcher = new ConnectionDispatcher(hostname, port, FAWKES_CID_NETWORKLOGGER);
00102   } else {
00103     __connection_dispatcher = new ConnectionDispatcher(FAWKES_CID_NETWORKLOGGER);
00104   }
00105
00106   append_column("Level",     __record.loglevel);
00107   append_column("Time",      __record.time);
00108   int compcol = append_column("Component", __record.component);
00109   int msgcol  = append_column("Message",   __record.message);
00110
00111   // We stored the number of columns, for an index (which starts at 0) we need
00112   // to subtract 1
00113   compcol -= 1;
00114   msgcol  -= 1;
00115
00116   Glib::ListHandle<Gtk::TreeViewColumn *> columns = get_columns();
00117   int colnum = -1;
00118   for (Glib::ListHandle<Gtk::TreeViewColumn *>::iterator c = columns.begin(); c != columns.end(); ++c) {
00119     ++colnum;
00120     Gtk::CellRenderer *cell_renderer = (*c)->get_first_cell_renderer();
00121     Gtk::CellRendererText *text_renderer = dynamic_cast<Gtk::CellRendererText *>(cell_renderer);
00122     if ( text_renderer ) {
00123 #ifdef GLIBMM_PROPERTIES_ENABLED
00124       if ( (colnum == compcol) || (colnum == msgcol) ) {
00125         (*c)->set_resizable();
00126       }
00127       if ( colnum == compcol ) {
00128         text_renderer->property_ellipsize().set_value(Pango::ELLIPSIZE_END);
00129       }
00130
00131       (*c)->add_attribute(text_renderer->property_background_gdk(), __record.background);
00132       (*c)->add_attribute(text_renderer->property_foreground_gdk(), __record.foreground);
00133 #else
00134       (*c)->add_attribute(*text_renderer, "background-gdk", __record.background);
00135       (*c)->add_attribute(*text_renderer, "foreground-gdk", __record.background);
00136 #endif
00137     }
00138   }
00139
00140   __connection_dispatcher->signal_message_received().connect(sigc::mem_fun(*this, &LogView::on_message_received));
00141   __connection_dispatcher->signal_connected().connect(sigc::mem_fun(*this, &LogView::on_client_connected));
00142   __connection_dispatcher->signal_disconnected().connect(sigc::mem_fun(*this, &LogView::on_client_disconnected));
00143   signal_expose_event().connect_notify(sigc::mem_fun(*this, &LogView::on_expose_notify));
00144 }
00145
00146 
00147 /** Set FawkesNetworkClient instance.
00148  * @param client Fawkes network client to use
00149  */
00150 void
00151 LogView::set_client(FawkesNetworkClient *client)
00152 {
00153   FawkesNetworkClient *c = __connection_dispatcher->get_client();
00154   if (c && c->connected()) {
00155     FawkesNetworkMessage *msg = new FawkesNetworkMessage(FAWKES_CID_NETWORKLOGGER,
00156                                                          NetworkLogger::MSGTYPE_UNSUBSCRIBE);
00157     c->enqueue(msg);
00158     msg->unref();
00159   }
00160   __connection_dispatcher->set_client(client);
00161   if (client && client->connected()) {
00162     FawkesNetworkMessage *msg = new FawkesNetworkMessage(FAWKES_CID_NETWORKLOGGER,
00163                                                          NetworkLogger::MSGTYPE_SUBSCRIBE);
00164     client->enqueue(msg);
00165     msg->unref();
00166   }
00167 }
00168
00169 
00170 /** Get the used FawkesNetworkClient.
00171  * @return Fawkes network client instance
00172  */
00173 FawkesNetworkClient *
00174 LogView::get_client()
00175 {
00176   return __connection_dispatcher->get_client();
00177 }
00178
00179 
00180 /** Get ConnectionDispatcher instance that is used internally.
00181  * @return connection dispatcher
00182  */
00183 ConnectionDispatcher *
00184 LogView::get_connection_dispatcher() const
00185 {
00186   return __connection_dispatcher;
00187 }
00188
00189 
00190 /** Clear all records. */
00191 void
00192 LogView::clear()
00193 {
00194   __list->clear();
00195 }
00196
00197 
00198 /** Event handler when row inserted.
00199  * @param path path to element
00200  * @param iter iterator to inserted element
00201  */
00202 void
00203 LogView::on_row_inserted(const Gtk::TreeModel::Path& path,
00204                          const Gtk::TreeModel::iterator& iter)
00205 {
00206   Gtk::TreeModel::Path vstart, vend;
00207   Gtk::TreeModel::Path prev = path;
00208   get_visible_range(vstart, vend);
00209   prev = path;
00210   if (! get_visible_range(vstart, vend) ||
00211       ( prev.prev() &&
00212         ((vend == prev) ||
00213          (__have_recently_added_path && (__recently_added_path == prev)))) ) {
00214     scroll_to_row(path);
00215
00216     // the recently added stuff is required if multiple rows are inserted at
00217     // a time. In this case the widget wasn't redrawn and get_visible_range() does
00218     // not give the desired result and we have to "advance" it manually
00219     __have_recently_added_path = true;
00220     __recently_added_path = path;
00221   }
00222 }
00223
00224
00225 void
00226 LogView::on_expose_notify(GdkEventExpose *event)
00227 {
00228   __have_recently_added_path = false;
00229 }
00230
00231 void
00232 LogView::on_client_connected()
00233 {
00234   FawkesNetworkClient *c = __connection_dispatcher->get_client();
00235   if (c && c->connected()) {
00236     FawkesNetworkMessage *msg = new FawkesNetworkMessage(FAWKES_CID_NETWORKLOGGER,
00237                                                          NetworkLogger::MSGTYPE_SUBSCRIBE);
00238     c->enqueue(msg);
00239     msg->unref();
00240     struct timeval t;
00241     gettimeofday(&t, NULL);
00242     append_message(Logger::LL_DEBUG, t, "LogView", false, "Connected");
00243   }
00244 }
00245
00246 void
00247 LogView::on_client_disconnected()
00248 {
00249   struct timeval t;
00250   gettimeofday(&t, NULL);
00251   append_message(Logger::LL_ERROR, t, "LogView", false, "*** Connection died. ***");
00252 }
00253
00254 
00255 /** Append a single message.
00256  * @param log_level log level
00257  * @param t time of the message
00258  * @param component component string for the message
00259  * @param is_exception true if essage was produced via an exception
00260  * @param message log message
00261  */
00262 void
00263 LogView::append_message(Logger::LogLevel log_level, struct timeval t,
00264                         const char *component, bool is_exception,
00265                         const char *message)
00266 {
00267   const char *loglevel;
00268   const char *timestr;
00269   char* time = NULL;
00270   Gdk::Color color;
00271   bool set_foreground = false;
00272   bool set_background = false;
00273
00274   switch ( log_level ) {
00275   case Logger::LL_DEBUG:
00276     loglevel = "DEBUG";
00277     color.set_rgb_p(0.4, 0.4, 0.4);
00278     set_foreground = true;
00279     break;
00280   case Logger::LL_INFO:
00281     loglevel = "INFO";
00282     break;
00283   case Logger::LL_WARN:
00284     loglevel = "WARN";
00285     color.set_rgb_p(1.0, 1.0, 0.7);
00286     set_background = true;
00287     break;
00288   case Logger::LL_ERROR:
00289     loglevel = "ERROR";
00290     color.set_rgb_p(1.0, 0.8, 0.8);
00291     set_background = true;
00292     break;
00293   default:
00294     loglevel = "NONE?";
00295     color.set_rgb_p(1.0, 0.0, 0.0);
00296     set_background = true;
00297     break;
00298   }
00299
00300   struct tm time_tm;
00301   localtime_r(&(t.tv_sec), &time_tm);
00302   if (asprintf(&time, "%02d:%02d:%02d.%06ld", time_tm.tm_hour,
00303                time_tm.tm_min, time_tm.tm_sec, t.tv_usec) == -1) {
00304     timestr = "OutOfMemory";
00305   } else {
00306     timestr = time;
00307   }
00308
00309   Gtk::TreeModel::Row row  = *__list->append();
00310   row[__record.loglevel]   = loglevel;
00311   row[__record.time]       = timestr;
00312   row[__record.component]  = component;
00313   if ( is_exception ) {
00314     row[__record.message]    = std::string("[EXCEPTION] ") + message;
00315   } else {
00316     row[__record.message]    = message;
00317   }
00318   if ( set_foreground )  row[__record.foreground] = color;
00319   if ( set_background )  row[__record.background] = color;
00320
00321   if (time) free(time);
00322 }
00323 
00324 /** Message received event handler.
00325  * @param msg Fawkes network message just recveived.
00326  */
00327 void
00328 LogView::on_message_received(FawkesNetworkMessage *msg)
00329 {
00330   if ( (msg->cid() == FAWKES_CID_NETWORKLOGGER) &&
00331        (msg->msgid() == NetworkLogger::MSGTYPE_LOGMESSAGE) ) {
00332
00333     NetworkLoggerMessageContent* content = msg->msgc<NetworkLoggerMessageContent>();
00334
00335     append_message(content->get_loglevel(), content->get_time(),
00336                    content->get_component(),
00337                    content->is_exception(), content->get_message());
00338
00339     delete content;
00340   }
00341 }
00342 
00343 /** @class LogView::LogRecord <gui_utils/logview.h>
00344  * TreeView record for LogView.
00345  */
00346 
00347 /** Constructor. */
00348 LogView::LogRecord::LogRecord()
00349 {
00350   add(loglevel);
00351   add(time);
00352   add(component);
00353   add(message);
00354   add(foreground);
00355   add(background);
00356 }
00357
00358
00359
00360 } // end namespace fawkes