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