Fawkes API  Fawkes Development Version
fuse_image_list_widget.cpp
1 
2 /***************************************************************************
3  * fuse_image_list_widget.cpp - Fuse image list widget
4  *
5  * Created: Mon Mar 24 21:12:56 2008
6  * Copyright 2008 Daniel Beck
7  *
8  ****************************************************************************/
9 
10 /* This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18  * GNU Library General Public License for more details.
19  *
20  * Read the full text in the LICENSE.GPL file in the doc directory.
21  */
22 
23 #include "fuse_image_list_widget.h"
24 
25 #include <fvutils/net/fuse_message.h>
26 #include <fvutils/net/fuse_imagelist_content.h>
27 
28 #include <netinet/in.h>
29 #include <cstring>
30 #include <sstream>
31 
32 using namespace fawkes;
33 
34 namespace firevision {
35 #if 0 /* just to make Emacs auto-indent happy */
36 }
37 #endif
38 
39 /** @class FuseImageListWidget <fvwidgets/fuse_image_list_widget.h>
40  * This widget displays all available Fuse images in a tree view. It also can check
41  * the registered host for new images, regularly.
42  * @author Daniel Beck
43  */
44 
45 /** Constructor. */
46 FuseImageListWidget::FuseImageListWidget()
47 {
48  m_chk_compression = NULL;
49  m_chk_auto_update = NULL;
50 
51  m_cur_client.active = false;
52 
53  m_new_clients.clear();
54  m_delete_clients.clear();
55 
56  m_image_list = Gtk::TreeStore::create(m_image_record);
57 
58  m_signal_get_image_list.connect( sigc::mem_fun( *this, &FuseImageListWidget::get_image_list) );
59  m_signal_delete_clients.connect( sigc::mem_fun( *this, &FuseImageListWidget::delete_clients) );
60  m_signal_update_image_l.connect( sigc::mem_fun( *this, &FuseImageListWidget::update_image_list) );
61 
62 #if GTK_VERSION_LT(3,0)
63  m_popup_menu = Gtk::manage( new Gtk::Menu() );
64  Gtk::Menu::MenuList& menulist = m_popup_menu->items();
65  menulist.push_back( Gtk::Menu_Helpers::MenuElem("Update now", sigc::mem_fun( *this, &FuseImageListWidget::update_image_list) ) );
66  menulist.push_back( Gtk::Menu_Helpers::SeparatorElem() );
67  menulist.push_back( Gtk::Menu_Helpers::MenuElem("Add host manually", sigc::mem_fun( *this, &FuseImageListWidget::on_add_host_manually) ) );
68 #endif
69 
70  set_image_list_trv(this);
71 }
72 
73 /** Destructor. */
74 FuseImageListWidget::~FuseImageListWidget()
75 {
76  FuseClient* c;
77  m_new_clients.lock();
78  while (m_new_clients.size() != 0)
79  {
80  c = m_new_clients.front().client;
81  m_new_clients.pop_front();
82  c->disconnect();
83  c->cancel();
84  c->join();
85  delete c;
86  }
87  m_new_clients.unlock();
88 
89  if (m_cur_client.active)
90  {
91  m_cur_client.active = false;
92  m_delete_clients.push_locked(m_cur_client.client);
93  }
94  delete_clients();
95 }
96 
97 /** Call this method when new Fountain services are discovered.
98  * @param name the name of the service
99  * @param host_name the host the service is running on
100  * @param port the port the service is running on
101  */
102 void
103 FuseImageListWidget::add_fountain_service( const char* name,
104  const char* host_name,
105  uint32_t port )
106 {
107  // check whether it's already in the tree
108  m_img_list_mutex.lock();
109  Gtk::TreeModel::Children children = m_image_list->children();
110  for ( Gtk::TreeModel::Children::iterator iter = children.begin();
111  iter != children.end(); ++iter )
112  {
113  Gtk::TreeModel::Row row = *iter;
114  if ( row[m_image_record.service_name] == Glib::ustring(name) )
115  {
116  m_img_list_mutex.unlock();
117  return;
118  }
119  }
120  m_img_list_mutex.unlock();
121 
122  // check if there is already a waiting request for this service
123  m_new_clients.lock();
124  for ( LockList<ClientData>::iterator iter = m_new_clients.begin();
125  iter != m_new_clients.end(); ++iter )
126  {
127  if (name == iter->service_name)
128  {
129  m_new_clients.unlock();
130  return;
131  }
132  }
133  m_new_clients.unlock();
134 
135  ClientData data;
136  data.client = 0;
137  data.service_name = std::string(name);
138  data.host_name = std::string(host_name);
139  data.port = port;
140  data.active = false;
141 
142  m_new_clients.push_back_locked(data);
143  m_signal_get_image_list();
144 }
145 
146 /** Call this method when a Fountain service vanishes.
147  * @param name the name of the service
148  */
149 void
150 FuseImageListWidget::remove_fountain_service(const char* name)
151 {
152  m_img_list_mutex.lock();
153  Gtk::TreeModel::Children children = m_image_list->children();
154  Gtk::TreeModel::Children::iterator iter = children.begin();
155  while ( iter != children.end() )
156  {
157  Gtk::TreeModel::Row row = *iter;
158  if ( row[m_image_record.service_name] == Glib::ustring(name) )
159  {
160  iter = m_image_list->erase(iter);
161  m_image_list->row_deleted( m_image_list->get_path(iter) );
162  }
163  else
164  {
165  ++iter;
166  }
167  }
168  m_img_list_mutex.unlock();
169 }
170 
171 /** Assign the TreeView widget to hold the list of images.
172  * @param trv a Gtk::TreeView
173  */
174 void
175 FuseImageListWidget::set_image_list_trv(Gtk::TreeView* trv)
176 {
177  m_img_list_mutex.lock();
178  m_trv_image_list = trv;
179  m_trv_image_list->set_model(m_image_list);
180  m_trv_image_list->append_column("asdf", m_image_record.display_text);
181  m_trv_image_list->set_headers_visible(false);
182  m_trv_image_list->signal_event().connect( sigc::mem_fun(*this, &FuseImageListWidget::on_image_event) );
183  m_trv_image_list->signal_cursor_changed().connect( sigc::mem_fun(*this, &FuseImageListWidget::on_image_selected) );
184  m_img_list_mutex.unlock();
185 }
186 
187 /** Assign the CheckButton to toggle the compression.
188  * @param chk a Gtk::CheckButton
189  */
190 void
191 FuseImageListWidget::set_toggle_compression_chk(Gtk::CheckButton* chk)
192 {
193  m_chk_compression = chk;
194  m_chk_compression->signal_toggled().connect( sigc::mem_fun(*this, &FuseImageListWidget::on_compression_toggled) );
195 }
196 
197 /** Assign the CheckButton that enables/disables the auto update function.
198  * @param chk a Gtk::CheckButton
199  */
200 void
201 FuseImageListWidget::set_auto_update_chk(Gtk::CheckButton* chk)
202 {
203  m_chk_auto_update = chk;
204  m_chk_auto_update->signal_toggled().connect( sigc::mem_fun(*this, &FuseImageListWidget::on_auto_update_toggled) );
205 }
206 
207 /** Access the Dispatcher that is signalled when a new image is selected in the list of
208  * images.
209  * @return reference to the Dispatcher that is activated when an image is selected in the
210  * list of images
211  */
212 Glib::Dispatcher&
213 FuseImageListWidget::image_selected()
214 {
215  return m_signal_image_selected;
216 }
217 
218 /** Get auto-update status.
219  * @return true if auto-update is activated
220  */
221 bool
222 FuseImageListWidget::auto_update()
223 {
224  return m_auto_update;
225 }
226 
227 /** Set the auto-update status.
228  * @param active (de-)activate auto-update
229  * @param interval_sec the update interval in seconds
230  */
231 void
232 FuseImageListWidget::set_auto_update(bool active, unsigned int interval_sec)
233 {
234  m_auto_update = active;
235  m_interval_sec = interval_sec;
236 
237  if (m_auto_update)
238  {
239 #if GLIBMM_MAJOR_VERSION > 2 || ( GLIBMM_MAJOR_VERSION == 2 && GLIBMM_MINOR_VERSION >= 14 )
240  m_timeout_conn = Glib::signal_timeout().connect_seconds( sigc::mem_fun(*this, &FuseImageListWidget::on_update_timeout),
241  m_interval_sec);
242 #else
243  m_timeout_conn = Glib::signal_timeout().connect(sigc::mem_fun(*this, &FuseImageListWidget::on_update_timeout), m_interval_sec);
244 #endif
245  }
246  else m_timeout_conn.disconnect();
247 }
248 
249 /** Get the host name, port, and image id of the selected image.
250  * @param host_name the host name of the selected image
251  * @param port the port of the selected image
252  * @param image_id the id of the selected image
253  * @param compression true if compression shall be switched on
254  * @return true if references could be assigned
255  */
256 bool
257 FuseImageListWidget::get_selected_image( std::string& host_name, unsigned short& port,
258  std::string& image_id, bool& compression )
259 {
260  if ( !m_trv_image_list )
261  { return false; }
262 
263  m_img_list_mutex.lock();
264  Glib::RefPtr<Gtk::TreeSelection> selection = m_trv_image_list->get_selection();
265 
266  if ( selection->count_selected_rows() != 1 )
267  {
268  m_img_list_mutex.unlock();
269  return false;
270  }
271 
272  Gtk::TreeModel::iterator iter = selection->get_selected();
273  host_name = iter->get_value(m_image_record.host_name);
274  port = iter->get_value(m_image_record.port);
275  image_id = iter->get_value(m_image_record.image_id);
276  m_img_list_mutex.unlock();
277 
278  if (m_chk_compression)
279  { compression = m_chk_compression->get_active(); }
280  else
281  { compression = false; }
282 
283  return true;
284 }
285 
286 
287 bool
288 FuseImageListWidget::on_image_event(GdkEvent *event)
289 {
290  GdkEventButton btn = event->button;
291  if (btn.type == GDK_BUTTON_PRESS && btn.button == 3) {
292 #if GTK_VERSION_LT(3,0)
293  m_popup_menu->popup(btn.button, btn.time);
294 #endif
295  return true;
296  }
297  return false;
298 }
299 
300 void
301 FuseImageListWidget::on_image_selected()
302 {
303  m_img_list_mutex.lock();
304  Glib::RefPtr<Gtk::TreeSelection> selection = m_trv_image_list->get_selection();
305 
306  Gtk::TreeModel::iterator iter = selection->get_selected();
307  Glib::ustring image_id;
308  image_id = (*iter)[m_image_record.image_id];
309  m_img_list_mutex.unlock();
310 
311  if ((image_id != m_cur_image_id) && (image_id != "invalid"))
312  {
313  m_cur_image_id = image_id;
314  m_signal_image_selected();
315  }
316 }
317 
318 void
319 FuseImageListWidget::on_auto_update_toggled()
320 {
321  set_auto_update( m_chk_auto_update->get_active() );
322 }
323 
324 void
325 FuseImageListWidget::on_compression_toggled()
326 {
327  m_signal_image_selected();
328 }
329 
330 void
331 FuseImageListWidget::get_image_list()
332 {
333  if (m_cur_client.active)
334  // communication in progress
335  { return; }
336 
337  m_new_clients.lock();
338  if (m_new_clients.size() == 0)
339  {
340  if (m_auto_update)
341  {
342 #if GLIBMM_MAJOR_VERSION > 2 || ( GLIBMM_MAJOR_VERSION == 2 && GLIBMM_MINOR_VERSION >= 14 )
343  m_timeout_conn = Glib::signal_timeout().connect_seconds( sigc::mem_fun(*this, &FuseImageListWidget::on_update_timeout),
344  m_interval_sec);
345 #else
346  m_timeout_conn = Glib::signal_timeout().connect(sigc::mem_fun(*this, &FuseImageListWidget::on_update_timeout), m_interval_sec);
347 #endif
348  }
349  m_new_clients.unlock();
350  return;
351  }
352 
353  m_cur_client = m_new_clients.front();
354  m_cur_client.active = true;
355  m_new_clients.pop_front();
356  m_new_clients.unlock();
357 
358  try
359  {
360  m_cur_client.client = new FuseClient( m_cur_client.host_name.c_str(),
361  m_cur_client.port, this );
362  m_cur_client.client->connect();
363  m_cur_client.client->start();
364  m_cur_client.client->enqueue(FUSE_MT_GET_IMAGE_LIST);
365  }
366  catch (Exception& e)
367  {
368  e.print_trace();
369  m_cur_client.client->cancel();
370  m_cur_client.client->join();
371  delete m_cur_client.client;
372  m_cur_client.active = false;
373  }
374 }
375 
376 void
377 FuseImageListWidget::delete_clients()
378 {
379  FuseClient* c = 0;
380 
381  m_delete_clients.lock();
382  while (m_delete_clients.size() != 0)
383  {
384  c = m_delete_clients.front();
385  m_delete_clients.pop();
386 
387  c->disconnect();
388  c->cancel();
389  c->join();
390  delete c;
391  }
392  m_delete_clients.unlock();
393 }
394 
395 bool
396 FuseImageListWidget::on_update_timeout()
397 {
398  m_signal_update_image_l();
399  return m_auto_update;
400 }
401 
402 void
403 FuseImageListWidget::update_image_list()
404 {
405  m_timeout_conn.disconnect();
406  if (m_img_list_mutex.try_lock())
407  {
408  Gtk::TreeModel::Children children = m_image_list->children();
409  for ( Gtk::TreeModel::Children::iterator iter = children.begin();
410  iter != children.end(); ++iter )
411  {
412  if ( (*iter)[m_image_record.image_id] == "invalid" )
413  {
414  ClientData data;
415  data.client = 0;
416  Glib::ustring service_name = (*iter)[m_image_record.service_name];
417  Glib::ustring host_name = (*iter)[m_image_record.host_name];
418  data.service_name = std::string( service_name.c_str() );
419  data.host_name = std::string( host_name.c_str() );
420  data.port = (*iter)[m_image_record.port];
421  data.active = false;
422 
423  m_new_clients.push_back_locked(data);
424  }
425  }
426  m_img_list_mutex.unlock();
427  }
428 
429  m_signal_get_image_list();
430 }
431 
432 void
433 FuseImageListWidget::fuse_invalid_server_version(uint32_t local_version,
434  uint32_t remote_version) throw()
435 {
436  printf("Invalid versions: local: %u remote: %u\n", local_version, remote_version);
437 }
438 
439 void
440 FuseImageListWidget::fuse_connection_established () throw()
441 {
442 }
443 
444 void
445 FuseImageListWidget::fuse_connection_died() throw()
446 {
447  if (m_cur_client.active)
448  {
449  m_delete_clients.push_locked(m_cur_client.client);
450  m_cur_client.active = false;
451  }
452 
453  m_signal_delete_clients();
454 }
455 
456 void
457 FuseImageListWidget::fuse_inbound_received (FuseNetworkMessage *m) throw()
458 {
459  switch ( m->type() )
460  {
461  case FUSE_MT_IMAGE_LIST:
462  {
463  // check whether it's already in the tree
464  m_img_list_mutex.lock();
465  Gtk::TreeModel::Children children = m_image_list->children();
466  Gtk::TreeModel::Children::iterator iter = children.begin();
467  while ( iter != children.end() )
468  {
469  Gtk::TreeModel::Row row = *iter;
470  if ( row[m_image_record.service_name] == Glib::ustring(m_cur_client.service_name) )
471  {
472  iter = m_image_list->erase(iter);
473  }
474  else
475  {
476  ++iter;
477  }
478  }
479 
480  try
481  {
482  FuseImageListContent* content = m->msgc<FuseImageListContent>();
483  if ( content->has_next() )
484  {
485  Gtk::TreeModel::Row row = *m_image_list->append();
486  row[m_image_record.display_text] = Glib::ustring(m_cur_client.host_name);
487  row[m_image_record.service_name] = Glib::ustring(m_cur_client.service_name);
488  row[m_image_record.host_name] = Glib::ustring(m_cur_client.host_name);
489  row[m_image_record.port] = m_cur_client.port;
490  row[m_image_record.colorspace] = 0;
491  row[m_image_record.image_id] = "invalid";
492  row[m_image_record.width] = 0;
493  row[m_image_record.height] = 0;
494  row[m_image_record.buffer_size] = 0;
495 
496  Gtk::TreeModel::Path path = m_image_list->get_path(row);
497 
498  while ( content->has_next() )
499  {
500  FUSE_imageinfo_t* image_info = content->next();
501  char image_id[IMAGE_ID_MAX_LENGTH + 1];
502  image_id[IMAGE_ID_MAX_LENGTH] = '\0';
503  strncpy(image_id, image_info->image_id, IMAGE_ID_MAX_LENGTH);
504 
505  Gtk::TreeModel::Row childrow = *m_image_list->append( row.children() );
506  childrow[m_image_record.display_text] = Glib::ustring(image_id);
507  childrow[m_image_record.service_name] = Glib::ustring(m_cur_client.service_name);
508  childrow[m_image_record.host_name] = Glib::ustring(m_cur_client.host_name);
509  childrow[m_image_record.port] = m_cur_client.port;
510  childrow[m_image_record.colorspace] = ntohl(image_info->colorspace);
511  childrow[m_image_record.image_id] = Glib::ustring(image_id);
512  childrow[m_image_record.width] = ntohl(image_info->width);
513  childrow[m_image_record.height] = ntohl(image_info->height);
514  childrow[m_image_record.buffer_size] = ntohl(image_info->buffer_size);
515  }
516 
517  m_trv_image_list->expand_row(path, false);
518  }
519 
520  delete content;
521  }
522  catch (Exception& e)
523  {
524  e.print_trace();
525  }
526 
527  m_img_list_mutex.unlock();
528 
529  m_delete_clients.push_locked(m_cur_client.client);
530  m_cur_client.active = false;
531 
532  m_signal_get_image_list();
533  m_signal_delete_clients();
534 
535  break;
536  }
537 
538  default:
539  printf("Unhandled message type\n");
540  }
541 }
542 
543 void
544 FuseImageListWidget::on_add_host_manually()
545 {
546  Gtk::Dialog* add_host =
547  new Gtk::Dialog("Add host manually", true);
548  add_host->add_button(Gtk::Stock::ADD, Gtk::RESPONSE_OK);
549  add_host->add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
550 
551  Gtk::Table* tab = Gtk::manage( new Gtk::Table(2, 2, false) );
552  Gtk::Label* hlab = Gtk::manage( new Gtk::Label("Host:") );
553  Gtk::Label* plab = Gtk::manage( new Gtk::Label("Port:") );
554  Gtk::Entry* hent = Gtk::manage( new Gtk::Entry() );
555  Gtk::HBox* pbox = Gtk::manage( new Gtk::HBox() );
556 
557 #if GTK_VERSION_GE(3,0)
558  Glib::RefPtr<Gtk::Adjustment> prange = Gtk::Adjustment::create(2208, 1, 65535);
559 #else
560  Gtk::Adjustment prange(2208, 1, 65535);
561 #endif
562  Gtk::SpinButton *pent = Gtk::manage( new Gtk::SpinButton(prange) );
563 
564  char * fawkes_ip = getenv("FAWKES_IP");
565  if (fawkes_ip) hent->set_text(std::string(fawkes_ip).append(":2208"));
566  else hent->set_text("localhost:2208");
567 
568  pbox->pack_start(*pent, false, false, 0);
569  tab->attach(*hlab, 1, 2, 1, 2);
570  tab->attach(*plab, 1, 2, 2, 3);
571  tab->attach(*hent, 2, 3, 1, 2);
572  tab->attach(*pbox, 2, 3, 2, 3);
573 
574  add_host->get_vbox()->pack_start(*tab, false, true, 0);
575  add_host->get_vbox()->show_all_children(true);
576 
577  if (add_host->run() == Gtk::RESPONSE_OK) {
578  std::string name = "fountain on ";
579  std::string host = hent->get_text();
580  unsigned short port = 2208;
581 
582  Glib::ustring::size_type pos;
583  if ((pos = host.find(':')) != Glib::ustring::npos)
584  {
585  Glib::ustring tmp_host = "";
586  unsigned int tmp_port = 1234567; //Greater than max port num (i.e. 65535)
587  std::istringstream is(host.replace(pos, 1, " "));
588  is >> tmp_host;
589  is >> tmp_port;
590 
591  if (tmp_port != 1234567 && tmp_host.size())
592  {
593  host = tmp_host;
594  port = tmp_port;
595  }
596  }
597 
598  name.append(host);
599  add_fountain_service(name.c_str(), host.c_str(), port);
600  }
601 
602  add_host->hide();
603  delete add_host;
604 }
605 
606 } // end namespace firevision
void connect()
Connect.
void disconnect()
Disconnect.
Image info message.
Definition: fuse.h:165
Fawkes library namespace.
uint32_t width
width in pixels
Definition: fuse.h:169
uint32_t colorspace
color space
Definition: fuse.h:167
char image_id[IMAGE_ID_MAX_LENGTH]
image ID
Definition: fuse.h:166
FUSE Network Message.
Definition: fuse_message.h:41
Base class for exceptions in Fawkes.
Definition: exception.h:36
List with a lock.
Definition: thread.h:40
bool has_next()
Check if another image info is available.
void cancel()
Cancel a thread.
Definition: thread.cpp:651
void print_trace()
Prints trace to stderr.
Definition: exception.cpp:619
uint32_t height
height in pixels
Definition: fuse.h:170
void join()
Join the thread.
Definition: thread.cpp:610
uint32_t buffer_size
size of following image buffer in bytes
Definition: fuse.h:171
FUSE_imageinfo_t * next()
Get next image info.