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
32using namespace fawkes;
33
34namespace 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. */
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
72}
73
74/** Destructor. */
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 */
101void
102FuseImageListWidget::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 */
141void
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 */
162void
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 */
180void
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 */
191void
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 */
204Glib::Dispatcher &
206{
207 return m_signal_image_selected;
208}
209
210/** Get auto-update status.
211 * @return true if auto-update is activated
212 */
213bool
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 */
223void
224FuseImageListWidget::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 */
249bool
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
282bool
283FuseImageListWidget::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
295void
296FuseImageListWidget::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
312void
313FuseImageListWidget::on_auto_update_toggled()
314{
315 set_auto_update(m_chk_auto_update->get_active());
316}
317
318void
319FuseImageListWidget::on_compression_toggled()
320{
321 m_signal_image_selected();
322}
323
324void
325FuseImageListWidget::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
367void
368FuseImageListWidget::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
385bool
386FuseImageListWidget::on_update_timeout()
387{
388 m_signal_update_image_l();
389 return m_auto_update;
390}
391
392void
393FuseImageListWidget::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
419void
421 uint32_t remote_version) noexcept
422{
423 printf("Invalid versions: local: %u remote: %u\n", local_version, remote_version);
424}
425
426void
428{
429}
430
431void
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
442void
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
517void
518FuseImageListWidget::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
Base class for exceptions in Fawkes.
Definition: exception.h:36
void print_trace() noexcept
Prints trace to stderr.
Definition: exception.cpp:601
List with a lock.
Definition: lock_list.h:45
virtual void unlock() const
Unlock list.
Definition: lock_list.h:138
void push_back_locked(const Type &x)
Push element to list at back with lock protection.
Definition: lock_list.h:145
virtual void lock() const
Lock list.
Definition: lock_list.h:124
bool try_lock()
Tries to lock the mutex.
Definition: mutex.cpp:117
void lock()
Lock this mutex.
Definition: mutex.cpp:87
void unlock()
Unlock the mutex.
Definition: mutex.cpp:131
void join()
Join the thread.
Definition: thread.cpp:597
void cancel()
Cancel a thread.
Definition: thread.cpp:646
void disconnect()
Disconnect.
bool has_next()
Check if another image info is available.
FUSE_imageinfo_t * next()
Get next image info.
void fuse_invalid_server_version(uint32_t local_version, uint32_t remote_version) noexcept
Invalid version string received.
bool get_selected_image(std::string &host_name, unsigned short &port, std::string &image_id, bool &compression)
Get the host name, port, and image id of the selected image.
void set_image_list_trv(Gtk::TreeView *trv)
Assign the TreeView widget to hold the list of images.
void fuse_connection_died() noexcept
Connection died.
void fuse_inbound_received(FuseNetworkMessage *m) noexcept
Message received.
void set_auto_update(bool active, unsigned int interval_sec=5)
Set the auto-update status.
void add_fountain_service(const char *name, const char *host_name, uint32_t port)
Call this method when new Fountain services are discovered.
bool auto_update()
Get auto-update status.
Glib::Dispatcher & image_selected()
Access the Dispatcher that is signalled when a new image is selected in the list of images.
void set_auto_update_chk(Gtk::CheckButton *chk)
Assign the CheckButton that enables/disables the auto update function.
void fuse_connection_established() noexcept
Connection has been established.
void set_toggle_compression_chk(Gtk::CheckButton *chk)
Assign the CheckButton to toggle the compression.
void remove_fountain_service(const char *name)
Call this method when a Fountain service vanishes.
FUSE Network Message.
Definition: fuse_message.h:40
Fawkes library namespace.
Image info message.
Definition: fuse.h:168
uint32_t colorspace
color space
Definition: fuse.h:170
uint32_t height
height in pixels
Definition: fuse.h:173
uint32_t width
width in pixels
Definition: fuse.h:172
char image_id[IMAGE_ID_MAX_LENGTH]
image ID
Definition: fuse.h:169
uint32_t buffer_size
size of following image buffer in bytes
Definition: fuse.h:174