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