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 #ifdef HAVE_GLADEMM
00063 /** Constructor.
00064  * Special ctor to be used with Glade's get_widget_derived().
00065  * @param cobject Gtk C object
00066  * @param refxml Glade's XML reference
00067  */
00068 LogView::LogView(BaseObjectType* cobject,
00069                  const Glib::RefPtr<Gnome::Glade::Xml>& refxml)
00070   : Gtk::TreeView(cobject)
00071 {
00072   ctor();
00073 }
00074 #endif
00075 
00076 
00077 /** Destructor. */
00078 LogView::~LogView()
00079 {
00080   FawkesNetworkClient *c = __connection_dispatcher->get_client();
00081   if (c && c->connected()) {
00082     FawkesNetworkMessage *msg = new FawkesNetworkMessage(FAWKES_CID_NETWORKLOGGER,
00083                                                          NetworkLogger::MSGTYPE_UNSUBSCRIBE);
00084     c->enqueue(msg);
00085   }
00086   delete __connection_dispatcher;
00087 }
00088 
00089 
00090 /** Internal constructor method. */
00091 void
00092 LogView::ctor(const char *hostname, unsigned short int port)
00093 {
00094   __list = Gtk::ListStore::create(__record);
00095   __have_recently_added_path = false;
00096 
00097   __list->signal_row_inserted().connect(sigc::mem_fun(*this, &LogView::on_row_inserted));
00098   set_model(__list);
00099   get_selection()->set_mode(Gtk::SELECTION_NONE);
00100 
00101   if ( (hostname != NULL) && (port != 0) ) {
00102     __connection_dispatcher = new ConnectionDispatcher(hostname, port, FAWKES_CID_NETWORKLOGGER);
00103   } else {
00104     __connection_dispatcher = new ConnectionDispatcher(FAWKES_CID_NETWORKLOGGER);
00105   }
00106 
00107   append_column("Level",     __record.loglevel);
00108   append_column("Time",      __record.time);
00109   int compcol = append_column("Component", __record.component);
00110   int msgcol  = append_column("Message",   __record.message);
00111 
00112   // We stored the number of columns, for an index (which starts at 0) we need
00113   // to subtract 1
00114   compcol -= 1;
00115   msgcol  -= 1;
00116 
00117   Glib::ListHandle<Gtk::TreeViewColumn *> columns = get_columns();
00118   int colnum = -1;
00119   for (Glib::ListHandle<Gtk::TreeViewColumn *>::iterator c = columns.begin(); c != columns.end(); ++c) {
00120     ++colnum;
00121     Gtk::CellRenderer *cell_renderer = (*c)->get_first_cell_renderer();
00122     Gtk::CellRendererText *text_renderer = dynamic_cast<Gtk::CellRendererText *>(cell_renderer);
00123     if ( text_renderer ) {
00124 #ifdef GLIBMM_PROPERTIES_ENABLED
00125       if ( (colnum == compcol) || (colnum == msgcol) ) {
00126         (*c)->set_resizable();
00127       }
00128       if ( colnum == compcol ) {
00129         text_renderer->property_ellipsize().set_value(Pango::ELLIPSIZE_END);
00130       }
00131 
00132       (*c)->add_attribute(text_renderer->property_background_gdk(), __record.background);
00133       (*c)->add_attribute(text_renderer->property_foreground_gdk(), __record.foreground);
00134 #else
00135       (*c)->add_attribute(*text_renderer, "background-gdk", __record.background);
00136       (*c)->add_attribute(*text_renderer, "foreground-gdk", __record.background);
00137 #endif
00138     }
00139   }
00140 
00141   __connection_dispatcher->signal_message_received().connect(sigc::mem_fun(*this, &LogView::on_message_received));
00142   __connection_dispatcher->signal_connected().connect(sigc::mem_fun(*this, &LogView::on_client_connected));
00143   __connection_dispatcher->signal_disconnected().connect(sigc::mem_fun(*this, &LogView::on_client_disconnected));
00144   signal_expose_event().connect_notify(sigc::mem_fun(*this, &LogView::on_expose_notify));
00145 }
00146 
00147 
00148 /** Set FawkesNetworkClient instance.
00149  * @param client Fawkes network client to use
00150  */
00151 void
00152 LogView::set_client(FawkesNetworkClient *client)
00153 {
00154   FawkesNetworkClient *c = __connection_dispatcher->get_client();
00155   if (c && c->connected()) {
00156     FawkesNetworkMessage *msg = new FawkesNetworkMessage(FAWKES_CID_NETWORKLOGGER,
00157                                                          NetworkLogger::MSGTYPE_UNSUBSCRIBE);
00158     c->enqueue(msg);
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   }
00166 }
00167 
00168 
00169 /** Get the used FawkesNetworkClient.
00170  * @return Fawkes network client instance
00171  */
00172 FawkesNetworkClient *
00173 LogView::get_client()
00174 {
00175   return __connection_dispatcher->get_client();
00176 }
00177 
00178 
00179 /** Get ConnectionDispatcher instance that is used internally.
00180  * @return connection dispatcher
00181  */
00182 ConnectionDispatcher *
00183 LogView::get_connection_dispatcher() const
00184 {
00185   return __connection_dispatcher;
00186 }
00187 
00188 
00189 /** Clear all records. */
00190 void
00191 LogView::clear()
00192 {
00193   __list->clear();
00194 }
00195 
00196 
00197 /** Event handler when row inserted.
00198  * @param path path to element
00199  * @param iter iterator to inserted element
00200  */
00201 void
00202 LogView::on_row_inserted(const Gtk::TreeModel::Path& path,
00203                          const Gtk::TreeModel::iterator& iter)
00204 {
00205   Gtk::TreeModel::Path vstart, vend;
00206   Gtk::TreeModel::Path prev = path;
00207   get_visible_range(vstart, vend);
00208   prev = path;
00209   if (! get_visible_range(vstart, vend) ||
00210       ( prev.prev() &&
00211         ((vend == prev) ||
00212          (__have_recently_added_path && (__recently_added_path == prev)))) ) {
00213     scroll_to_row(path);
00214 
00215     // the recently added stuff is required if multiple rows are inserted at
00216     // a time. In this case the widget wasn't redrawn and get_visible_range() does
00217     // not give the desired result and we have to "advance" it manually
00218     __have_recently_added_path = true;
00219     __recently_added_path = path;
00220   }
00221 }
00222 
00223 
00224 void
00225 LogView::on_expose_notify(GdkEventExpose *event)
00226 {
00227   __have_recently_added_path = false;
00228 }
00229 
00230 void
00231 LogView::on_client_connected()
00232 {
00233   FawkesNetworkClient *c = __connection_dispatcher->get_client();
00234   if (c && c->connected()) {
00235     FawkesNetworkMessage *msg = new FawkesNetworkMessage(FAWKES_CID_NETWORKLOGGER,
00236                                                          NetworkLogger::MSGTYPE_SUBSCRIBE);
00237     c->enqueue(msg);
00238     struct timeval t;
00239     gettimeofday(&t, NULL);
00240     append_message(Logger::LL_DEBUG, t, "LogView", false, "Connected");
00241   }
00242 }
00243 
00244 void
00245 LogView::on_client_disconnected()
00246 {
00247   struct timeval t;
00248   gettimeofday(&t, NULL);
00249   append_message(Logger::LL_ERROR, t, "LogView", false, "*** Connection died. ***");
00250 }
00251 
00252 
00253 /** Append a single message.
00254  * @param log_level log level
00255  * @param t time of the message
00256  * @param component component string for the message
00257  * @param is_exception true if essage was produced via an exception
00258  * @param message log message
00259  */
00260 void
00261 LogView::append_message(Logger::LogLevel log_level, struct timeval t,
00262                         const char *component, bool is_exception,
00263                         const char *message)
00264 {
00265   const char *loglevel;
00266   const char *timestr;
00267   char* time = NULL;
00268   Gdk::Color color;
00269   bool set_foreground = false;
00270   bool set_background = false;
00271 
00272   switch ( log_level ) {
00273   case Logger::LL_DEBUG:
00274     loglevel = "DEBUG";
00275     color.set_rgb_p(0.4, 0.4, 0.4);
00276     set_foreground = true;
00277     break;
00278   case Logger::LL_INFO:
00279     loglevel = "INFO";
00280     break;
00281   case Logger::LL_WARN:
00282     loglevel = "WARN";
00283     color.set_rgb_p(1.0, 1.0, 0.7);
00284     set_background = true;
00285     break;
00286   case Logger::LL_ERROR:
00287     loglevel = "ERROR";
00288     color.set_rgb_p(1.0, 0.8, 0.8);
00289     set_background = true;
00290     break;
00291   default:
00292     loglevel = "NONE?";
00293     color.set_rgb_p(1.0, 0.0, 0.0);
00294     set_background = true;
00295     break;
00296   }
00297 
00298   struct tm time_tm;
00299   localtime_r(&(t.tv_sec), &time_tm);
00300   if (asprintf(&time, "%02d:%02d:%02d.%06ld", time_tm.tm_hour,
00301                time_tm.tm_min, time_tm.tm_sec, t.tv_usec) == -1) {
00302     timestr = "OutOfMemory";
00303   } else {
00304     timestr = time;
00305   }
00306 
00307   Gtk::TreeModel::Row row  = *__list->append();
00308   row[__record.loglevel]   = loglevel;
00309   row[__record.time]       = timestr;
00310   row[__record.component]  = component;
00311   if ( is_exception ) {
00312     row[__record.message]    = std::string("[EXCEPTION] ") + message;
00313   } else {
00314     row[__record.message]    = message;
00315   }
00316   if ( set_foreground )  row[__record.foreground] = color;
00317   if ( set_background )  row[__record.background] = color;
00318 
00319   if (time) free(time);
00320 }
00321 
00322 /** Message received event handler.
00323  * @param msg Fawkes network message just recveived.
00324  */
00325 void
00326 LogView::on_message_received(FawkesNetworkMessage *msg)
00327 {
00328   if ( (msg->cid() == FAWKES_CID_NETWORKLOGGER) &&
00329        (msg->msgid() == NetworkLogger::MSGTYPE_LOGMESSAGE) ) {
00330 
00331     NetworkLoggerMessageContent* content = msg->msgc<NetworkLoggerMessageContent>();
00332 
00333     append_message(content->get_loglevel(), content->get_time(),
00334                    content->get_component(),
00335                    content->is_exception(), content->get_message());
00336 
00337     delete content;
00338   }
00339 }
00340 
00341 /** @class LogView::LogRecord <gui_utils/logview.h>
00342  * TreeView record for LogView.
00343  */
00344 
00345 /** Constructor. */
00346 LogView::LogRecord::LogRecord()
00347 {
00348   add(loglevel);
00349   add(time);
00350   add(component);
00351   add(message);
00352   add(foreground);
00353   add(background);
00354 }
00355 
00356 
00357 
00358 } // end namespace fawkes