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