Fawkes API  Fawkes Development Version
fuse_image_list_widget.cpp
00001 
00002 /***************************************************************************
00003  *  fuse_image_list_widget.cpp - Fuse image list widget
00004  *
00005  *  Created: Mon Mar 24 21:12:56 2008
00006  *  Copyright  2008  Daniel Beck
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.
00014  *
00015  *  This program is distributed in the hope that it will be useful,
00016  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00017  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00018  *  GNU Library General Public License for more details.
00019  *
00020  *  Read the full text in the LICENSE.GPL file in the doc directory.
00021  */
00022 
00023 #include "fuse_image_list_widget.h"
00024 
00025 #include <fvutils/net/fuse_message.h>
00026 #include <fvutils/net/fuse_imagelist_content.h>
00027 
00028 #include <netinet/in.h>
00029 #include <cstring>
00030 #include <sstream>
00031 
00032 using namespace fawkes;
00033 
00034 namespace firevision {
00035 #if 0 /* just to make Emacs auto-indent happy */
00036 }
00037 #endif
00038 
00039 /** @class FuseImageListWidget <fvwidgets/fuse_image_list_widget.h>
00040  * This widget displays all available Fuse images in a tree view. It also can check
00041  * the registered host for new images, regularly.
00042  * @author Daniel Beck
00043  */
00044 
00045 /** Constructor. */
00046 FuseImageListWidget::FuseImageListWidget()
00047 {
00048   m_chk_compression   = NULL;
00049   m_chk_auto_update   = NULL;
00050 
00051   m_cur_client.active = false;
00052 
00053   m_new_clients.clear();
00054   m_delete_clients.clear();
00055 
00056   m_image_list = Gtk::TreeStore::create(m_image_record);
00057 
00058   m_signal_get_image_list.connect( sigc::mem_fun( *this, &FuseImageListWidget::get_image_list) );
00059   m_signal_delete_clients.connect( sigc::mem_fun( *this, &FuseImageListWidget::delete_clients) );
00060   m_signal_update_image_l.connect( sigc::mem_fun( *this, &FuseImageListWidget::update_image_list) );
00061 
00062 #if GTK_VERSION_LT(3,0)
00063   m_popup_menu = Gtk::manage( new Gtk::Menu() );
00064   Gtk::Menu::MenuList& menulist = m_popup_menu->items();
00065   menulist.push_back( Gtk::Menu_Helpers::MenuElem("Update now", sigc::mem_fun( *this, &FuseImageListWidget::update_image_list) ) );
00066   menulist.push_back( Gtk::Menu_Helpers::SeparatorElem() );
00067   menulist.push_back( Gtk::Menu_Helpers::MenuElem("Add host manually", sigc::mem_fun( *this, &FuseImageListWidget::on_add_host_manually) ) );
00068 #endif
00069 
00070   set_image_list_trv(this);
00071 }
00072 
00073 /** Destructor. */
00074 FuseImageListWidget::~FuseImageListWidget()
00075 {
00076   FuseClient* c;
00077   m_new_clients.lock();
00078   while (m_new_clients.size() != 0)
00079     {
00080       c = m_new_clients.front().client;
00081       m_new_clients.pop_front();
00082       c->disconnect();
00083       c->cancel();
00084       c->join();
00085       delete c;
00086     }
00087   m_new_clients.unlock();
00088 
00089   if (m_cur_client.active)
00090     {
00091       m_cur_client.active = false;
00092       m_delete_clients.push_locked(m_cur_client.client);
00093     }
00094   delete_clients();
00095 }
00096 
00097 /** Call this method when new Fountain services are discovered.
00098  * @param name the name of the service
00099  * @param host_name the host the service is running on
00100  * @param port the port the service is running on
00101  */
00102 void
00103 FuseImageListWidget::add_fountain_service( const char* name,
00104                                            const char* host_name,
00105                                            uint32_t port )
00106 {
00107   // check whether it's already in the tree
00108   m_img_list_mutex.lock();
00109   Gtk::TreeModel::Children children = m_image_list->children();
00110   for ( Gtk::TreeModel::Children::iterator iter = children.begin();
00111         iter != children.end(); ++iter )
00112     {
00113       Gtk::TreeModel::Row row = *iter;
00114       if ( row[m_image_record.service_name] == Glib::ustring(name) )
00115         { 
00116           m_img_list_mutex.unlock();
00117           return; 
00118         }
00119     }
00120   m_img_list_mutex.unlock();
00121 
00122   // check if there is already a waiting request for this service
00123   m_new_clients.lock();
00124   for ( LockList<ClientData>::iterator iter = m_new_clients.begin();
00125         iter != m_new_clients.end(); ++iter )
00126     {
00127       if (name == iter->service_name)
00128         { 
00129           m_new_clients.unlock();
00130           return;
00131         }
00132     }
00133   m_new_clients.unlock();
00134 
00135   ClientData data;
00136   data.client = 0;
00137   data.service_name = std::string(name);
00138   data.host_name = std::string(host_name);
00139   data.port = port;
00140   data.active = false;
00141   
00142   m_new_clients.push_back_locked(data);
00143   m_signal_get_image_list();
00144 }
00145 
00146 /** Call this method when a Fountain service vanishes.
00147  * @param name the name of the service
00148  */
00149 void
00150 FuseImageListWidget::remove_fountain_service(const char* name)
00151 {
00152   m_img_list_mutex.lock();
00153   Gtk::TreeModel::Children children = m_image_list->children();
00154   Gtk::TreeModel::Children::iterator iter = children.begin();
00155   while ( iter != children.end() )
00156     {
00157       Gtk::TreeModel::Row row = *iter;
00158       if ( row[m_image_record.service_name] == Glib::ustring(name) )
00159         {
00160           iter = m_image_list->erase(iter);
00161           m_image_list->row_deleted( m_image_list->get_path(iter) );
00162         }
00163       else
00164         {
00165           ++iter;
00166         }
00167     }
00168   m_img_list_mutex.unlock();
00169 }
00170 
00171 /** Assign the TreeView widget to hold the list of images.
00172  * @param trv a Gtk::TreeView
00173  */
00174 void
00175 FuseImageListWidget::set_image_list_trv(Gtk::TreeView* trv)
00176 {
00177   m_img_list_mutex.lock();
00178   m_trv_image_list = trv;
00179   m_trv_image_list->set_model(m_image_list);
00180   m_trv_image_list->append_column("asdf", m_image_record.display_text);
00181   m_trv_image_list->set_headers_visible(false);
00182   m_trv_image_list->signal_event().connect( sigc::mem_fun(*this, &FuseImageListWidget::on_image_event) );
00183   m_trv_image_list->signal_cursor_changed().connect( sigc::mem_fun(*this, &FuseImageListWidget::on_image_selected) );
00184   m_img_list_mutex.unlock();
00185 }
00186 
00187 /** Assign the CheckButton to toggle the compression.
00188  * @param chk a Gtk::CheckButton
00189  */
00190 void
00191 FuseImageListWidget::set_toggle_compression_chk(Gtk::CheckButton* chk)
00192 {
00193   m_chk_compression = chk;
00194   m_chk_compression->signal_toggled().connect( sigc::mem_fun(*this, &FuseImageListWidget::on_compression_toggled) );
00195 }
00196 
00197 /** Assign the CheckButton that enables/disables the auto update function.
00198  * @param chk a Gtk::CheckButton
00199  */
00200 void
00201 FuseImageListWidget::set_auto_update_chk(Gtk::CheckButton* chk)
00202 {
00203   m_chk_auto_update = chk;
00204   m_chk_auto_update->signal_toggled().connect( sigc::mem_fun(*this, &FuseImageListWidget::on_auto_update_toggled) );
00205 }
00206 
00207 /** Access the Dispatcher that is signalled when a new image is selected in the list of
00208  * images.
00209  * @return reference to the Dispatcher that is activated when an image is selected in the
00210  *         list of images
00211  */
00212 Glib::Dispatcher&
00213 FuseImageListWidget::image_selected()
00214 {
00215   return m_signal_image_selected;
00216 }
00217 
00218 /** Get auto-update status.
00219  * @return true if auto-update is activated
00220  */
00221 bool
00222 FuseImageListWidget::auto_update()
00223 {
00224   return m_auto_update;
00225 }
00226 
00227 /** Set the auto-update status.
00228  * @param active (de-)activate auto-update
00229  * @param interval_sec the update interval in seconds
00230  */
00231 void
00232 FuseImageListWidget::set_auto_update(bool active, unsigned int interval_sec)
00233 {
00234   m_auto_update  = active;
00235   m_interval_sec = interval_sec;
00236 
00237   if (m_auto_update)
00238     {
00239 #if GLIBMM_MAJOR_VERSION > 2 || ( GLIBMM_MAJOR_VERSION == 2 && GLIBMM_MINOR_VERSION >= 14 )
00240       m_timeout_conn = Glib::signal_timeout().connect_seconds( sigc::mem_fun(*this, &FuseImageListWidget::on_update_timeout),
00241                                                                m_interval_sec);
00242 #else
00243       m_timeout_conn = Glib::signal_timeout().connect(sigc::mem_fun(*this, &FuseImageListWidget::on_update_timeout), m_interval_sec);
00244 #endif
00245     }
00246   else m_timeout_conn.disconnect();
00247 }
00248 
00249 /** Get the host name, port, and image id of the selected image.
00250  * @param host_name the host name of the selected image
00251  * @param port the port of the selected image
00252  * @param image_id the id of the selected image
00253  * @param compression true if compression shall be switched on
00254  * @return true if references could be assigned
00255  */
00256 bool
00257 FuseImageListWidget::get_selected_image( std::string& host_name, unsigned short& port,
00258                                          std::string& image_id, bool& compression )
00259 {
00260   if ( !m_trv_image_list )
00261     { return false; }
00262 
00263   m_img_list_mutex.lock();
00264   Glib::RefPtr<Gtk::TreeSelection> selection = m_trv_image_list->get_selection();
00265 
00266   if ( selection->count_selected_rows() != 1 )
00267     {
00268       m_img_list_mutex.unlock();
00269       return false;
00270     }
00271 
00272   Gtk::TreeModel::iterator iter = selection->get_selected();
00273   host_name = iter->get_value(m_image_record.host_name);
00274   port      = iter->get_value(m_image_record.port);
00275   image_id  = iter->get_value(m_image_record.image_id);
00276   m_img_list_mutex.unlock();
00277 
00278   if (m_chk_compression)
00279     { compression = m_chk_compression->get_active(); }
00280   else
00281     { compression = false; }
00282   
00283   return true;
00284 }
00285 
00286 
00287 bool
00288 FuseImageListWidget::on_image_event(GdkEvent *event)
00289 {
00290   GdkEventButton btn = event->button;
00291   if (btn.type == GDK_BUTTON_PRESS && btn.button == 3) {
00292 #if GTK_VERSION_LT(3,0)
00293     m_popup_menu->popup(btn.button, btn.time);
00294 #endif
00295     return true;
00296   }
00297   return false;
00298 }
00299 
00300 void
00301 FuseImageListWidget::on_image_selected()
00302 {
00303   m_img_list_mutex.lock();
00304   Glib::RefPtr<Gtk::TreeSelection> selection = m_trv_image_list->get_selection();
00305 
00306   Gtk::TreeModel::iterator iter = selection->get_selected();
00307   Glib::ustring image_id;
00308   image_id = (*iter)[m_image_record.image_id];
00309   m_img_list_mutex.unlock();
00310 
00311   if ((image_id != m_cur_image_id) && (image_id != "invalid"))
00312     {
00313       m_cur_image_id = image_id;
00314       m_signal_image_selected();
00315     }
00316 }
00317 
00318 void
00319 FuseImageListWidget::on_auto_update_toggled()
00320 {
00321   set_auto_update( m_chk_auto_update->get_active() );
00322 }
00323 
00324 void
00325 FuseImageListWidget::on_compression_toggled()
00326 {
00327   m_signal_image_selected();
00328 }
00329 
00330 void
00331 FuseImageListWidget::get_image_list()
00332 {
00333   if (m_cur_client.active)
00334     // communication in progress
00335     { return; }
00336 
00337   m_new_clients.lock();
00338   if (m_new_clients.size() == 0)
00339     {
00340       if (m_auto_update)
00341         {
00342 #if GLIBMM_MAJOR_VERSION > 2 || ( GLIBMM_MAJOR_VERSION == 2 && GLIBMM_MINOR_VERSION >= 14 )
00343           m_timeout_conn = Glib::signal_timeout().connect_seconds( sigc::mem_fun(*this, &FuseImageListWidget::on_update_timeout),
00344                                                                    m_interval_sec);
00345 #else
00346           m_timeout_conn = Glib::signal_timeout().connect(sigc::mem_fun(*this, &FuseImageListWidget::on_update_timeout), m_interval_sec);
00347 #endif
00348         }
00349       m_new_clients.unlock();
00350       return;
00351     }
00352 
00353   m_cur_client = m_new_clients.front();
00354   m_cur_client.active = true;
00355   m_new_clients.pop_front();
00356   m_new_clients.unlock();
00357 
00358   try
00359     {
00360       m_cur_client.client = new FuseClient( m_cur_client.host_name.c_str(), 
00361                                             m_cur_client.port, this );
00362       m_cur_client.client->connect();
00363       m_cur_client.client->start();
00364       m_cur_client.client->enqueue(FUSE_MT_GET_IMAGE_LIST);
00365     }
00366   catch (Exception& e)
00367     {
00368       e.print_trace();
00369       m_cur_client.client->cancel();
00370       m_cur_client.client->join();
00371       delete m_cur_client.client;
00372       m_cur_client.active = false;
00373     }
00374 }
00375 
00376 void
00377 FuseImageListWidget::delete_clients()
00378 {
00379   FuseClient* c = 0;
00380 
00381   m_delete_clients.lock();
00382   while (m_delete_clients.size() != 0)
00383     {
00384       c = m_delete_clients.front();
00385       m_delete_clients.pop();
00386 
00387       c->disconnect();
00388       c->cancel();
00389       c->join();
00390       delete c;
00391     }
00392   m_delete_clients.unlock();
00393 }
00394 
00395 bool
00396 FuseImageListWidget::on_update_timeout()
00397 {
00398   m_signal_update_image_l();
00399   return m_auto_update;
00400 }
00401 
00402 void
00403 FuseImageListWidget::update_image_list()
00404 {
00405   m_timeout_conn.disconnect();
00406   if (m_img_list_mutex.try_lock())
00407     {
00408       Gtk::TreeModel::Children children = m_image_list->children();
00409       for ( Gtk::TreeModel::Children::iterator iter = children.begin();
00410             iter != children.end(); ++iter )
00411         {
00412           if ( (*iter)[m_image_record.image_id] == "invalid" )
00413             {
00414               ClientData data;
00415               data.client = 0;
00416               Glib::ustring service_name = (*iter)[m_image_record.service_name];
00417               Glib::ustring host_name = (*iter)[m_image_record.host_name];
00418               data.service_name = std::string( service_name.c_str() );
00419               data.host_name = std::string( host_name.c_str() );
00420               data.port = (*iter)[m_image_record.port];
00421               data.active = false;
00422     
00423               m_new_clients.push_back_locked(data);
00424             }
00425         }
00426       m_img_list_mutex.unlock();
00427     }
00428 
00429   m_signal_get_image_list();
00430 }
00431 
00432 void
00433 FuseImageListWidget::fuse_invalid_server_version(uint32_t local_version,
00434                                                 uint32_t remote_version) throw()
00435 {
00436   printf("Invalid versions: local: %u   remote: %u\n", local_version, remote_version);
00437 }
00438 
00439 void
00440 FuseImageListWidget::fuse_connection_established () throw()
00441 {
00442 }
00443 
00444 void
00445 FuseImageListWidget::fuse_connection_died() throw()
00446 {
00447   if (m_cur_client.active)
00448     {
00449       m_delete_clients.push_locked(m_cur_client.client);
00450       m_cur_client.active = false;
00451     }
00452   
00453   m_signal_delete_clients();
00454 }
00455 
00456 void
00457 FuseImageListWidget::fuse_inbound_received (FuseNetworkMessage *m) throw()
00458 {
00459   switch ( m->type() )
00460     {
00461     case FUSE_MT_IMAGE_LIST:
00462       {
00463         // check whether it's already in the tree
00464         m_img_list_mutex.lock();
00465         Gtk::TreeModel::Children children = m_image_list->children();
00466         Gtk::TreeModel::Children::iterator iter = children.begin();
00467         while ( iter != children.end() )
00468           {
00469             Gtk::TreeModel::Row row = *iter;
00470             if ( row[m_image_record.service_name] == Glib::ustring(m_cur_client.service_name) )
00471               {
00472                 iter = m_image_list->erase(iter);
00473               }
00474             else
00475               {
00476                 ++iter;
00477               }
00478           }
00479 
00480         try
00481           {
00482             FuseImageListContent* content = m->msgc<FuseImageListContent>();
00483             if ( content->has_next() )
00484               {
00485                 Gtk::TreeModel::Row row = *m_image_list->append();
00486                 row[m_image_record.display_text] = Glib::ustring(m_cur_client.host_name);
00487                 row[m_image_record.service_name] = Glib::ustring(m_cur_client.service_name);
00488                 row[m_image_record.host_name]    = Glib::ustring(m_cur_client.host_name);
00489                 row[m_image_record.port]         = m_cur_client.port;
00490                 row[m_image_record.colorspace]   = 0;
00491                 row[m_image_record.image_id]     = "invalid";
00492                 row[m_image_record.width]        = 0;
00493                 row[m_image_record.height]       = 0;
00494                 row[m_image_record.buffer_size]  = 0;
00495                 
00496                 Gtk::TreeModel::Path path = m_image_list->get_path(row);
00497 
00498                 while ( content->has_next() )
00499                   {
00500                     FUSE_imageinfo_t* image_info = content->next();
00501                     char image_id[IMAGE_ID_MAX_LENGTH + 1];
00502                     image_id[IMAGE_ID_MAX_LENGTH] = '\0';
00503                     strncpy(image_id, image_info->image_id, IMAGE_ID_MAX_LENGTH);
00504 
00505                     Gtk::TreeModel::Row childrow = *m_image_list->append( row.children() );
00506                     childrow[m_image_record.display_text] = Glib::ustring(image_id);
00507                     childrow[m_image_record.service_name] = Glib::ustring(m_cur_client.service_name);
00508                     childrow[m_image_record.host_name]    = Glib::ustring(m_cur_client.host_name);
00509                     childrow[m_image_record.port]         = m_cur_client.port;
00510                     childrow[m_image_record.colorspace]   = ntohl(image_info->colorspace);
00511                     childrow[m_image_record.image_id]     = Glib::ustring(image_id);
00512                     childrow[m_image_record.width]        = ntohl(image_info->width);
00513                     childrow[m_image_record.height]       = ntohl(image_info->height);
00514                     childrow[m_image_record.buffer_size]  = ntohl(image_info->buffer_size);
00515                   }
00516 
00517                 m_trv_image_list->expand_row(path, false);
00518               }
00519 
00520             delete content;
00521           }
00522         catch (Exception& e)
00523           {
00524             e.print_trace();
00525           }
00526 
00527         m_img_list_mutex.unlock();
00528 
00529         m_delete_clients.push_locked(m_cur_client.client);
00530         m_cur_client.active = false;
00531 
00532         m_signal_get_image_list();
00533         m_signal_delete_clients();
00534         
00535         break;
00536       }
00537 
00538     default:
00539       printf("Unhandled message type\n");
00540     }
00541 }
00542 
00543 void
00544 FuseImageListWidget::on_add_host_manually()
00545 {
00546   Gtk::Dialog* add_host =
00547     new Gtk::Dialog("Add host manually", true);
00548   add_host->add_button(Gtk::Stock::ADD, Gtk::RESPONSE_OK);
00549   add_host->add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
00550 
00551   Gtk::Table* tab = Gtk::manage( new Gtk::Table(2, 2, false) );
00552   Gtk::Label* hlab = Gtk::manage( new Gtk::Label("Host:") );
00553   Gtk::Label* plab = Gtk::manage( new Gtk::Label("Port:") );
00554   Gtk::Entry* hent = Gtk::manage( new Gtk::Entry() );
00555   Gtk::HBox*  pbox = Gtk::manage( new Gtk::HBox() );
00556 
00557 #if GTK_VERSION_GE(3,0)
00558   Glib::RefPtr<Gtk::Adjustment> prange = Gtk::Adjustment::create(2208, 1, 65535);
00559 #else
00560   Gtk::Adjustment prange(2208, 1, 65535);
00561 #endif
00562   Gtk::SpinButton *pent = Gtk::manage( new Gtk::SpinButton(prange) );
00563 
00564   char * fawkes_ip = getenv("FAWKES_IP");
00565   if (fawkes_ip) hent->set_text(std::string(fawkes_ip).append(":2208"));
00566   else hent->set_text("localhost:2208");
00567 
00568   pbox->pack_start(*pent, false, false, 0);
00569   tab->attach(*hlab, 1, 2, 1, 2);
00570   tab->attach(*plab, 1, 2, 2, 3);
00571   tab->attach(*hent, 2, 3, 1, 2);
00572   tab->attach(*pbox, 2, 3, 2, 3);
00573 
00574   add_host->get_vbox()->pack_start(*tab, false, true, 0);
00575   add_host->get_vbox()->show_all_children(true);
00576 
00577   if (add_host->run() == Gtk::RESPONSE_OK) {
00578     std::string name = "fountain on ";
00579     std::string host = hent->get_text();
00580     unsigned short port = 2208;
00581 
00582     Glib::ustring::size_type pos;
00583     if ((pos = host.find(':')) != Glib::ustring::npos)
00584     {
00585       Glib::ustring tmp_host = "";
00586       unsigned int tmp_port = 1234567; //Greater than max port num (i.e. 65535)
00587       std::istringstream is(host.replace(pos, 1, " "));
00588       is >> tmp_host;
00589       is >> tmp_port;
00590 
00591       if (tmp_port != 1234567 && tmp_host.size())
00592       {
00593         host = tmp_host;
00594         port = tmp_port;
00595       }
00596     }
00597 
00598     name.append(host);
00599     add_fountain_service(name.c_str(), host.c_str(), port);
00600   }
00601 
00602   add_host->hide();
00603   delete add_host;
00604 }
00605 
00606 } // end namespace firevision